feat: New JWT in Every Authenticated Response

This commit is contained in:
Moriz Wahl 2021-09-09 11:43:14 +02:00
parent cae9de0f2b
commit 6811f45a1c
8 changed files with 81 additions and 30 deletions

View File

@ -4,6 +4,7 @@ import { AuthChecker } from 'type-graphql'
import decode from '../jwt/decode' import decode from '../jwt/decode'
import { apiGet } from '../apis/loginAPI' import { apiGet } from '../apis/loginAPI'
import CONFIG from '../config' import CONFIG from '../config'
import encode from '../jwt/encode'
/* eslint-disable-next-line @typescript-eslint/no-unused-vars */ /* eslint-disable-next-line @typescript-eslint/no-unused-vars */
export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => { export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info }, roles) => {
@ -14,6 +15,7 @@ export const isAuthorized: AuthChecker<any> = async ({ root, args, context, info
`${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`, `${CONFIG.LOGIN_API_URL}checkSessionState?session_id=${decoded.sessionId}`,
) )
context.sessionId = decoded.sessionId context.sessionId = decoded.sessionId
context.setHeaders.push({ key: 'token', value: encode(decoded.sessionId) })
return result.success return result.success
} }
} }

View File

@ -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
}

View File

@ -4,10 +4,10 @@
import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql' import { Resolver, Query, Args, Arg, Authorized, Ctx } from 'type-graphql'
import CONFIG from '../../config' import CONFIG from '../../config'
import { CheckUsernameResponse } from '../models/CheckUsernameResponse' import { CheckUsernameResponse } from '../models/CheckUsernameResponse'
import { User } from '../models/User'
import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode' import { LoginViaVerificationCode } from '../models/LoginViaVerificationCode'
import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse' import { SendPasswordResetEmailResponse } from '../models/SendPasswordResetEmailResponse'
import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse' import { UpdateUserInfosResponse } from '../models/UpdateUserInfosResponse'
import { LoginResponse } from '../models/LoginResponse'
import { import {
ChangePasswordArgs, ChangePasswordArgs,
CheckUsernameArgs, CheckUsernameArgs,
@ -16,12 +16,11 @@ import {
UpdateUserInfosArgs, UpdateUserInfosArgs,
} from '../inputs/LoginUserInput' } from '../inputs/LoginUserInput'
import { apiPost, apiGet } from '../../apis/loginAPI' import { apiPost, apiGet } from '../../apis/loginAPI'
import encode from '../../jwt/encode'
@Resolver() @Resolver()
export class UserResolver { export class UserResolver {
@Query(() => String) @Query(() => LoginResponse)
async login(@Args() { email, password }: UnsecureLoginArgs): Promise<string> { async login(@Args() { email, password }: UnsecureLoginArgs): Promise<LoginResponse> {
email = email.trim().toLowerCase() email = email.trim().toLowerCase()
const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password }) const result = await apiPost(CONFIG.LOGIN_API_URL + 'unsecureLogin', { email, password })
@ -30,10 +29,7 @@ export class UserResolver {
throw new Error(result.data) throw new Error(result.data)
} }
const data = result.data return new LoginResponse({ sessionId: result.data.session_id, user: result.data.user })
const sessionId = data.session_id
delete data.session_id
return encode({ sessionId, user: new User(data.user) })
} }
@Query(() => LoginViaVerificationCode) @Query(() => LoginViaVerificationCode)

View File

@ -22,14 +22,15 @@ import { isAuthorized } from './auth/auth'
const DB_VERSION = '0001-init_db' const DB_VERSION = '0001-init_db'
const context = (req: any) => { const context = (args: any) => {
const authorization = req.req.headers.authorization const authorization = args.req.headers.authorization
let token = null let token = null
if (authorization) { if (authorization) {
token = req.req.headers.authorization.replace(/^Bearer /, '') token = authorization.replace(/^Bearer /, '')
} }
const context = { const context = {
token, token,
setHeaders: [],
} }
return context return context
} }
@ -61,8 +62,24 @@ async function main() {
// Express Server // Express Server
const server = express() 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 // Apollo Server
const apollo = new ApolloServer({ schema, playground, context }) const apollo = new ApolloServer({ schema, playground, context, plugins })
apollo.applyMiddleware({ app: server }) apollo.applyMiddleware({ app: server })
// Start Server // Start Server

View File

@ -5,13 +5,9 @@ import jwt from 'jsonwebtoken'
import CONFIG from '../config/' import CONFIG from '../config/'
// Generate an Access Token // Generate an Access Token
export default function encode(data: any): string { export default function encode(sessionId: string): string {
const { user, sessionId } = data const token = jwt.sign({ sessionId }, CONFIG.JWT_SECRET, {
const { email, language, firstName, lastName } = user
const token = jwt.sign({ email, language, firstName, lastName, sessionId }, CONFIG.JWT_SECRET, {
expiresIn: CONFIG.JWT_EXPIRES_IN, expiresIn: CONFIG.JWT_EXPIRES_IN,
// issuer: CONFIG.GRAPHQL_URI,
// audience: CONFIG.CLIENT_URI,
subject: sessionId.toString(), subject: sessionId.toString(),
}) })
return token return token

View File

@ -2,7 +2,17 @@ import gql from 'graphql-tag'
export const login = gql` export const login = gql`
query($email: String!, $password: String!) { query($email: String!, $password: String!) {
login(email: $email, password: $password) login(email: $email, password: $password) {
token
user {
email
username
firstName
lastName
language
description
}
}
} }
` `

View File

@ -1,7 +1,7 @@
import Vue from 'vue' import Vue from 'vue'
import Vuex from 'vuex' import Vuex from 'vuex'
import createPersistedState from 'vuex-persistedstate' import createPersistedState from 'vuex-persistedstate'
import VueJwtDecode from 'vue-jwt-decode' // import VueJwtDecode from 'vue-jwt-decode'
Vue.use(Vuex) Vue.use(Vuex)
@ -30,15 +30,16 @@ export const mutations = {
} }
export const actions = { export const actions = {
login: ({ dispatch, commit }, token) => { login: ({ dispatch, commit }, data) => {
const decoded = VueJwtDecode.decode(token) // const decoded = VueJwtDecode.decode(data.token)
commit('token', token) const { user } = data
commit('email', decoded.email) commit('token', data.token)
commit('language', decoded.language) commit('email', user.email)
commit('username', decoded.username) commit('language', user.language)
commit('firstName', decoded.firstName) commit('username', user.username)
commit('lastName', decoded.lastName) commit('firstName', user.firstName)
commit('description', decoded.description) commit('lastName', user.lastName)
commit('description', user.description)
}, },
logout: ({ commit, state }) => { logout: ({ commit, state }) => {
commit('token', null) commit('token', null)

View File

@ -77,7 +77,17 @@ describe('Vuex store', () => {
describe('login', () => { describe('login', () => {
const commit = jest.fn() const commit = jest.fn()
const state = {} 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', () => { it('calls seven commits', () => {
login({ commit, state }, commitedData) login({ commit, state }, commitedData)