Merge pull request #1617 from gradido/test-logout

feat: Test Logout in User Resolver
This commit is contained in:
Moriz Wahl 2022-03-14 17:59:50 +01:00 committed by GitHub
commit 667d28ea05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 54 deletions

View File

@ -528,7 +528,7 @@ jobs:
report_name: Coverage Backend
type: lcov
result_path: ./backend/coverage/lcov.info
min_coverage: 53
min_coverage: 54
token: ${{ github.token }}
##########################################################################

View File

@ -13,35 +13,33 @@ import { ServerUser } from '@entity/ServerUser'
const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
context.role = ROLE_UNAUTHORIZED // unauthorized user
// is rights an inalienable right?
if ((<RIGHTS[]>rights).reduce((acc, right) => acc && INALIENABLE_RIGHTS.includes(right), true))
return true
// Do we have a token?
if (context.token) {
// Decode the token
const decoded = decode(context.token)
if (!decoded) {
// Are all rights requested public?
const isInalienable = (<RIGHTS[]>rights).reduce(
(acc, right) => acc && INALIENABLE_RIGHTS.includes(right),
true,
)
if (isInalienable) {
// If public dont throw and permit access
return true
} else {
// Throw on a protected route
throw new Error('403.13 - Client certificate revoked')
}
}
// Set context pubKey
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
// set new header token
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
const userRepository = await getCustomRepository(UserRepository)
if (!context.token) {
throw new Error('401 Unauthorized')
}
// Decode the token
const decoded = decode(context.token)
if (!decoded) {
throw new Error('403.13 - Client certificate revoked')
}
// Set context pubKey
context.pubKey = Buffer.from(decoded.pubKey).toString('hex')
// TODO - load from database dynamically & admin - maybe encode this in the token to prevent many database requests
// TODO this implementation is bullshit - two database queries cause our user identifiers are not aligned and vary between email, id and pubKey
const userRepository = await getCustomRepository(UserRepository)
try {
const user = await userRepository.findByPubkeyHex(context.pubKey)
const countServerUsers = await ServerUser.count({ email: user.email })
context.role = countServerUsers > 0 ? ROLE_ADMIN : ROLE_USER
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
} catch {
// in case the database query fails (user deleted)
throw new Error('401 Unauthorized')
}
// check for correct rights
@ -50,6 +48,8 @@ const isAuthorized: AuthChecker<any> = async ({ context }, rights) => {
throw new Error('401 Unauthorized')
}
// set new header token
context.setHeaders.push({ key: 'token', value: encode(decoded.pubKey) })
return true
}

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/explicit-module-boundary-types */
import { testEnvironment, createUser, headerPushMock, cleanDB } from '@test/helpers'
import { testEnvironment, createUser, headerPushMock, cleanDB, resetToken } from '@test/helpers'
import { createUserMutation, setPasswordMutation } from '@test/graphql'
import gql from 'graphql-tag'
import { GraphQLError } from 'graphql'
@ -31,6 +31,24 @@ jest.mock('@/apis/KlicktippController', () => {
let mutate: any, query: any, con: any
const loginQuery = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
beforeAll(async () => {
const testEnv = await testEnvironment()
mutate = testEnv.mutate
@ -284,24 +302,6 @@ describe('UserResolver', () => {
})
describe('login', () => {
const loginQuery = gql`
query ($email: String!, $password: String!, $publisherId: Int) {
login(email: $email, password: $password, publisherId: $publisherId) {
email
firstName
lastName
language
coinanimation
klickTipp {
newsletterState
}
hasElopage
publisherId
isAdmin
}
}
`
const variables = {
email: 'peter@lustig.de',
password: 'Aa12345_',
@ -328,7 +328,7 @@ describe('UserResolver', () => {
})
})
describe('user is in database', () => {
describe('user is in database and correct login data', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
@ -370,5 +370,81 @@ describe('UserResolver', () => {
expect(headerPushMock).toBeCalledWith({ key: 'token', value: expect.any(String) })
})
})
describe('user is in database and wrong password', () => {
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
})
afterAll(async () => {
await cleanDB()
})
it('returns an error', () => {
expect(
query({ query: loginQuery, variables: { ...variables, password: 'wrong' } }),
).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('No user with this credentials')],
}),
)
})
})
})
describe('logout', () => {
const logoutQuery = gql`
query {
logout
}
`
describe('unauthenticated', () => {
it('throws an error', async () => {
resetToken()
await expect(query({ query: logoutQuery })).resolves.toEqual(
expect.objectContaining({
errors: [new GraphQLError('401 Unauthorized')],
}),
)
})
})
describe('authenticated', () => {
const variables = {
email: 'peter@lustig.de',
password: 'Aa12345_',
}
beforeAll(async () => {
await createUser(mutate, {
email: 'peter@lustig.de',
firstName: 'Peter',
lastName: 'Lustig',
language: 'de',
publisherId: 1234,
})
await query({ query: loginQuery, variables })
})
afterAll(async () => {
await cleanDB()
})
it('returns true', async () => {
await expect(query({ query: logoutQuery })).resolves.toEqual(
expect.objectContaining({
data: { logout: 'true' },
errors: undefined,
}),
)
})
})
})
})

View File

@ -373,6 +373,8 @@ export class UserResolver {
/{code}/g,
emailOptIn.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName,
@ -380,11 +382,13 @@ export class UserResolver {
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
@ -414,6 +418,7 @@ export class UserResolver {
emailOptIn.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendAccountActivationEmail({
link: activationLink,
firstName: user.firstName,
@ -421,11 +426,13 @@ export class UserResolver {
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Account confirmation link: ${activationLink}`)
}
*/
await queryRunner.commitTransaction()
} catch (e) {
await queryRunner.rollbackTransaction()
@ -450,6 +457,7 @@ export class UserResolver {
optInCode.verificationCode.toString(),
)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const emailSent = await sendResetPasswordEmail({
link,
firstName: user.firstName,
@ -457,11 +465,13 @@ export class UserResolver {
email,
})
/* uncomment this, when you need the activation link on the console
// In case EMails are disabled log the activation link for the user
if (!emailSent) {
// eslint-disable-next-line no-console
console.log(`Reset password link: ${link}`)
}
*/
return true
}
@ -551,7 +561,9 @@ export class UserResolver {
} catch {
// TODO is this a problem?
// eslint-disable-next-line no-console
/* uncomment this, when you need the activation link on the console
console.log('Could not subscribe to klicktipp')
*/
}
}

View File

@ -3,18 +3,18 @@
import { createTestClient } from 'apollo-server-testing'
import createServer from '../src/server/createServer'
import { resetDB, initialize } from '@dbTools/helpers'
import { initialize } from '@dbTools/helpers'
import { createUserMutation, setPasswordMutation } from './graphql'
import { LoginEmailOptIn } from '@entity/LoginEmailOptIn'
import { User } from '@entity/User'
import { entities } from '@entity/index'
let token = ''
export const headerPushMock = jest.fn((t) => (token = t.value))
export const headerPushMock = jest.fn((t) => {
context.token = t.value
})
const context = {
token,
token: '',
setHeaders: {
push: headerPushMock,
forEach: jest.fn(),
@ -35,12 +35,11 @@ export const testEnvironment = async () => {
const mutate = testClient.mutate
const query = testClient.query
await initialize()
await resetDB()
return { mutate, query, con }
}
export const resetEntity = async (entity: any) => {
const items = await entity.find()
const items = await entity.find({ withDeleted: true })
if (items.length > 0) {
const ids = items.map((i: any) => i.id)
await entity.delete(ids)
@ -48,13 +47,18 @@ export const resetEntity = async (entity: any) => {
}
export const createUser = async (mutate: any, user: any) => {
// resetToken()
await mutate({ mutation: createUserMutation, variables: user })
const dbUser = await User.findOne({ where: { email: user.email } })
if (!dbUser) throw new Error('Ups, no user found')
const optin = await LoginEmailOptIn.findOne(dbUser.id)
const optin = await LoginEmailOptIn.findOne({ where: { userId: dbUser.id } })
if (!optin) throw new Error('Ups, no optin found')
await mutate({
mutation: setPasswordMutation,
variables: { password: 'Aa12345_', code: optin.verificationCode },
})
}
export const resetToken = () => {
context.token = ''
}