From 6811f45a1ce8937247a95abe0d122748c97fad87 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Thu, 9 Sep 2021 11:43:14 +0200 Subject: [PATCH] feat: New JWT in Every Authenticated Response --- backend/src/auth/auth.ts | 2 ++ backend/src/graphql/models/LoginResponse.ts | 19 ++++++++++++++ backend/src/graphql/resolvers/UserResolver.ts | 12 +++------ backend/src/index.ts | 25 ++++++++++++++++--- backend/src/jwt/encode.ts | 8 ++---- frontend/src/graphql/queries.js | 12 ++++++++- frontend/src/store/store.js | 21 ++++++++-------- frontend/src/store/store.test.js | 12 ++++++++- 8 files changed, 81 insertions(+), 30 deletions(-) create mode 100644 backend/src/graphql/models/LoginResponse.ts diff --git a/backend/src/auth/auth.ts b/backend/src/auth/auth.ts index 0d9014b15..ff7a07735 100644 --- a/backend/src/auth/auth.ts +++ b/backend/src/auth/auth.ts @@ -4,6 +4,7 @@ import { AuthChecker } from 'type-graphql' import decode from '../jwt/decode' import { apiGet } from '../apis/loginAPI' import CONFIG from '../config' +import encode from '../jwt/encode' /* eslint-disable-next-line @typescript-eslint/no-unused-vars */ export const isAuthorized: AuthChecker = async ({ root, args, context, info }, roles) => { @@ -14,6 +15,7 @@ export const isAuthorized: AuthChecker = async ({ root, args, context, info `${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`, ) context.sessionId = decoded.sessionId + context.setHeaders.push({ key: 'token', value: encode(decoded.sessionId) }) return result.success } } diff --git a/backend/src/graphql/models/LoginResponse.ts b/backend/src/graphql/models/LoginResponse.ts new file mode 100644 index 000000000..e40dce259 --- /dev/null +++ b/backend/src/graphql/models/LoginResponse.ts @@ -0,0 +1,19 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +/* eslint-disable @typescript-eslint/explicit-module-boundary-types */ +import { ObjectType, Field } from 'type-graphql' +import { User } from '../models/User' +import encode from '../../jwt/encode' + +@ObjectType() +export class LoginResponse { + constructor(json: any) { + this.token = encode(json.sessionId) + this.user = new User(json.user) + } + + @Field(() => String) + token: string + + @Field(() => User) + user: User +} diff --git a/backend/src/graphql/resolvers/UserResolver.ts b/backend/src/graphql/resolvers/UserResolver.ts index dd8240e02..c6f3ea0b0 100644 --- a/backend/src/graphql/resolvers/UserResolver.ts +++ b/backend/src/graphql/resolvers/UserResolver.ts @@ -4,10 +4,10 @@ import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql' import CONFIG from '../../config' import { CheckUsernameResponse } from '../models/CheckUsernameResponse' -import { User } from '../models/User' import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode' import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse' import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse' +import { LoginResponse } from '../models/LoginResponse' import { ChangePasswordArgs, CheckUsernameArgs, @@ -16,12 +16,11 @@ import { UpdateUserInfosArgs, } from '../inputs/LoginUserInput' import { apiPost, apiGet } from '../../apis/loginAPI' -import encode from '../../jwt/encode' @Resolver() export class UserResolver { - @Query(() => String) - async login(@Args() { email, password }: UnsecureLoginArgs): Promise { + @Query(() => LoginResponse) + async login(@Args() { email, password }: UnsecureLoginArgs): Promise { email = email.trim().toLowerCase() const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) @@ -30,10 +29,7 @@ export class UserResolver { throw new Error(result.data) } - const data = result.data - const sessionId = data.session_id - delete data.session_id - return encode({ sessionId, user: new User(data.user) }) + return new LoginResponse({ sessionId: result.data.session_id, user: result.data.user }) } @Query(() => LoginViaVerificationCode) diff --git a/backend/src/index.ts b/backend/src/index.ts index 067403508..50e2c0a60 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -22,14 +22,15 @@ import { isAuthorized } from './auth/auth' const DB_VERSION = '0001-init_db' -const context = (req: any) => { - const authorization = req.req.headers.authorization +const context = (args: any) => { + const authorization = args.req.headers.authorization let token = null if (authorization) { - token = req.req.headers.authorization.replace(/^Bearer /, '') + token = authorization.replace(/^Bearer /, '') } const context = { token, + setHeaders: [], } return context } @@ -61,8 +62,24 @@ async function main() { // Express Server const server = express() + const plugins = [ + { + requestDidStart() { + return { + willSendResponse(requestContext: any) { + const { setHeaders = [] } = requestContext.context + setHeaders.forEach(({ key, value }: { [key: string]: string }) => { + requestContext.response.http.headers.append(key, value) + }) + return requestContext + }, + } + }, + }, + ] + // Apollo Server - const apollo = new ApolloServer({ schema, playground, context }) + const apollo = new ApolloServer({ schema, playground, context, plugins }) apollo.applyMiddleware({ app: server }) // Start Server diff --git a/backend/src/jwt/encode.ts b/backend/src/jwt/encode.ts index 477644dc7..9c5145e6d 100644 --- a/backend/src/jwt/encode.ts +++ b/backend/src/jwt/encode.ts @@ -5,13 +5,9 @@ import jwt from 'jsonwebtoken' import CONFIG from '../config/' // Generate an Access Token -export default function encode(data: any): string { - const { user, sessionId } = data - const { email, language, firstName, lastName } = user - const token = jwt.sign({ email, language, firstName, lastName, sessionId }, CONFIG.JWT_SECRET, { +export default function encode(sessionId: string): string { + const token = jwt.sign({ sessionId }, CONFIG.JWT_SECRET, { expiresIn: CONFIG.JWT_EXPIRES_IN, - // issuer: CONFIG.GRAPHQL_URI, - // audience: CONFIG.CLIENT_URI, subject: sessionId.toString(), }) return token diff --git a/frontend/src/graphql/queries.js b/frontend/src/graphql/queries.js index 0aa11ce13..8c05c1ec3 100644 --- a/frontend/src/graphql/queries.js +++ b/frontend/src/graphql/queries.js @@ -2,7 +2,17 @@ import gql from 'graphql-tag' export const login = gql` query($email: String!, $password: String!) { - login(email: $email, password: $password) + login(email: $email, password: $password) { + token + user { + email + username + firstName + lastName + language + description + } + } } ` diff --git a/frontend/src/store/store.js b/frontend/src/store/store.js index 77c7096ad..8e7abcb08 100644 --- a/frontend/src/store/store.js +++ b/frontend/src/store/store.js @@ -1,7 +1,7 @@ import Vue from 'vue' import Vuex from 'vuex' import createPersistedState from 'vuex-persistedstate' -import VueJwtDecode from 'vue-jwt-decode' +// import VueJwtDecode from 'vue-jwt-decode' Vue.use(Vuex) @@ -30,15 +30,16 @@ export const mutations = { } export const actions = { - login: ({ dispatch, commit }, token) => { - const decoded = VueJwtDecode.decode(token) - commit('token', token) - commit('email', decoded.email) - commit('language', decoded.language) - commit('username', decoded.username) - commit('firstName', decoded.firstName) - commit('lastName', decoded.lastName) - commit('description', decoded.description) + login: ({ dispatch, commit }, data) => { + // const decoded = VueJwtDecode.decode(data.token) + const { user } = data + commit('token', data.token) + commit('email', user.email) + commit('language', user.language) + commit('username', user.username) + commit('firstName', user.firstName) + commit('lastName', user.lastName) + commit('description', user.description) }, logout: ({ commit, state }) => { commit('token', null) diff --git a/frontend/src/store/store.test.js b/frontend/src/store/store.test.js index 99a37451e..e935b40f1 100644 --- a/frontend/src/store/store.test.js +++ b/frontend/src/store/store.test.js @@ -77,7 +77,17 @@ describe('Vuex store', () => { describe('login', () => { const commit = jest.fn() const state = {} - const commitedData = 'token' + const commitedData = { + token: 'token', + user: { + email: 'user@example.org', + language: 'de', + username: 'peter', + firstName: 'Peter', + lastName: 'Lustig', + description: 'Nickelbrille', + }, + } it('calls seven commits', () => { login({ commit, state }, commitedData)