Merge branch '3030-feature-role-administration-backend' of github.com:gradido/gradido into 3030-feature-role-administration-backend

This commit is contained in:
Moriz Wahl 2023-07-13 14:39:05 +02:00
commit 736c9228a6
16 changed files with 1471 additions and 1264 deletions

View File

@ -33,7 +33,6 @@ jobs:
yarn && yarn dev_reset
cd ../backend
yarn && yarn seed
cd ..
- name: Boot up test system | docker-compose frontends
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps frontend admin nginx
@ -41,18 +40,35 @@ jobs:
- name: Boot up test system | docker-compose mailserver
run: docker-compose -f docker-compose.yml -f docker-compose.test.yml up --detach --no-deps mailserver
- name: Sleep for 15 seconds
run: sleep 15s
- name: End-to-end tests | prepare
run: |
wget --no-verbose -O /opt/cucumber-json-formatter "https://github.com/cucumber/json-formatter/releases/download/v19.0.0/cucumber-json-formatter-linux-386"
chmod +x /opt/cucumber-json-formatter
sudo ln -fs /opt/cucumber-json-formatter /usr/bin/cucumber-json-formatter
cd e2e-tests/
yarn
- name: End-to-end tests | run tests
id: e2e-tests
run: |
cd e2e-tests/
yarn
yarn run cypress run
- name: End-to-end tests | if tests failed, upload screenshots
- name: End-to-end tests | if tests failed, compile html report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
run: |
cd e2e-tests/
node create-cucumber-html-report.js
- name: End-to-end tests | if tests failed, get pr number
id: pr
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: 8BitJonny/gh-get-current-pr@2.2.0
- name: End-to-end tests | if tests failed, upload report
id: e2e-report
if: ${{ failure() && steps.e2e-tests.conclusion == 'failure' }}
uses: actions/upload-artifact@v3
with:
name: cypress-screenshots
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/screenshots/
name: cypress-report-pr-#${{ steps.pr.outputs.number }}
path: /home/runner/work/gradido/gradido/e2e-tests/cypress/reports/cucumber_html_report

View File

@ -36,10 +36,9 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
withDeleted: true,
relations: ['emailContact', 'userRoles'],
})
// console.log('isAuthorized user=', user)
context.user = user
context.role = ROLE_USER
if (user.userRoles && user.userRoles.length > 0) {
if (user.userRoles?.length > 0) {
switch (user.userRoles[0].role) {
case ROLE_NAMES.ROLE_NAME_ADMIN:
context.role = ROLE_ADMIN
@ -51,9 +50,7 @@ export const isAuthorized: AuthChecker<Context> = async ({ context }, rights) =>
context.role = ROLE_USER
}
}
// console.log('context.role=', context.role)
} catch {
// console.log('401 Unauthorized for decoded', decoded)
// in case the database query fails (user deleted)
throw new LogError('401 Unauthorized')
}

View File

@ -6,7 +6,7 @@ export class AdminUser {
constructor(user: User) {
this.firstName = user.firstName
this.lastName = user.lastName
this.role = user.userRoles ? user.userRoles[0].role : ''
this.role = user.userRoles.length > 0 ? user.userRoles[0].role : ''
}
@Field(() => String)

View File

@ -20,12 +20,7 @@ export class User {
this.createdAt = user.createdAt
this.language = user.language
this.publisherId = user.publisherId
if (user.userRoles) {
this.roles = [] as string[]
user.userRoles.forEach((userRole) => {
this.roles?.push(userRole.role)
})
}
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
this.klickTipp = null
this.hasElopage = null
this.hideAmountGDD = user.hideAmountGDD
@ -75,22 +70,16 @@ export class User {
@Field(() => Boolean, { nullable: true })
hasElopage: boolean | null
@Field(() => [String], { nullable: true })
roles: string[] | null
@Field(() => [String])
roles: string[]
@Field(() => Boolean)
isAdmin(): boolean {
if (this.roles) {
return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN)
}
return false
return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN)
}
@Field(() => Boolean)
isModerator(): boolean {
if (this.roles) {
return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR)
}
return false
return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR)
}
}

View File

@ -16,12 +16,7 @@ export class UserAdmin {
this.hasElopage = hasElopage
this.deletedAt = user.deletedAt
this.emailConfirmationSend = emailConfirmationSend
if (user.userRoles) {
this.roles = [] as string[]
user.userRoles.forEach((userRole) => {
this.roles?.push(userRole.role)
})
}
this.roles = user.userRoles?.map((userRole) => userRole.role) ?? []
}
@Field(() => Int)
@ -51,23 +46,17 @@ export class UserAdmin {
@Field(() => String, { nullable: true })
emailConfirmationSend: string | null
@Field(() => [String], { nullable: true })
roles: string[] | null
@Field(() => [String])
roles: string[]
@Field(() => Boolean)
isAdmin(): boolean {
if (this.roles) {
return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN)
}
return false
return this.roles.includes(ROLE_NAMES.ROLE_NAME_ADMIN)
}
@Field(() => Boolean)
isModerator(): boolean {
if (this.roles) {
return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR)
}
return false
return this.roles.includes(ROLE_NAMES.ROLE_NAME_MODERATOR)
}
}

View File

@ -165,7 +165,7 @@ describe('UserResolver', () => {
createdAt: expect.any(Date),
// emailChecked: false,
language: 'de',
userRoles: expect.any(Array),
userRoles: [],
deletedAt: null,
publisherId: 1234,
referrerId: null,
@ -350,10 +350,6 @@ describe('UserResolver', () => {
peter.userRoles[0].userId = peter.id
await peter.userRoles[0].save()
peter = await User.findOneOrFail({
where: { id: user[0].id },
relations: ['userRoles'],
})
// date statement
const actualDate = new Date()

View File

@ -69,6 +69,7 @@ import { getUserCreations } from './util/creations'
import { findUserByIdentifier } from './util/findUserByIdentifier'
import { findUsers } from './util/findUsers'
import { getKlicktippState } from './util/getKlicktippState'
import { setUserRole, deleteUserRole } from './util/modifyUserRole'
import { validateAlias } from './util/validateAlias'
const LANGUAGES = ['de', 'en', 'es', 'fr', 'nl']
@ -135,7 +136,6 @@ export class UserResolver {
logger.info(`login with ${email}, ***, ${publisherId} ...`)
email = email.trim().toLowerCase()
const dbUser = await findUserByEmail(email)
// console.log('login dbUser=', dbUser)
if (dbUser.deletedAt) {
throw new LogError('This user was permanently deleted. Contact support for questions', dbUser)
}
@ -224,7 +224,6 @@ export class UserResolver {
// check if user with email still exists?
email = email.trim().toLowerCase()
if (await checkEmailExists(email)) {
// console.log('email still exists! email', email)
const foundUser = await findUserByEmail(email)
logger.info('DbUser.findOne', email, foundUser)
@ -738,31 +737,13 @@ export class UserResolver {
throw new LogError('Administrator can not change his own role')
}
// if user role(s) should be deleted by role=null as parameter
if (role === null && user.userRoles) {
if (user.userRoles.length > 0) {
// remove all roles of the user
await UserRole.delete({ userId: user.id })
user.userRoles.length = 0
} else if (user.userRoles.length === 0) {
throw new LogError('User is already an usual user')
}
if (role === null) {
await deleteUserRole(user)
} else if (isUserInRole(user, role)) {
throw new LogError('User already has role=', role)
} else {
await setUserRole(user, role)
}
// if role shoud be set
if (role) {
if (user.userRoles === undefined) {
user.userRoles = [] as UserRole[]
}
if (user.userRoles.length < 1) {
user.userRoles.push(UserRole.create())
}
user.userRoles[0].createdAt = new Date()
user.userRoles[0].role = role
user.userRoles[0].userId = user.id
await UserRole.save(user.userRoles[0])
}
// await user.save()
await EVENT_ADMIN_USER_ROLE_SET(user, moderator)
const newUser = await DbUser.findOne({ where: { id: userId }, relations: ['userRoles'] })
return newUser?.userRoles ? newUser.userRoles[0].role : null
@ -899,10 +880,10 @@ const canEmailResend = (updatedAt: Date): boolean => {
return !isTimeExpired(updatedAt, CONFIG.EMAIL_CODE_REQUEST_TIME)
}
export function isUserInRole(user: DbUser, role: string | null): boolean {
if (user?.userRoles) {
for (const usrRole of user.userRoles) {
if (usrRole.role === role) {
export function isUserInRole(user: DbUser, role: string): boolean {
if (user && role) {
for (const userRole of user.userRoles) {
if (userRole.role === role) {
return true
}
}

View File

@ -0,0 +1,30 @@
import { User as DbUser } from '@entity/User'
import { UserRole } from '@entity/UserRole'
import { LogError } from '@/server/LogError'
export async function setUserRole(user: DbUser, role: string | null): Promise<void> {
// if role should be set
if (role) {
// in case user has still no associated userRole
if (user.userRoles.length < 1) {
// instanciate a userRole
user.userRoles.push(UserRole.create())
}
// and initialize the userRole
user.userRoles[0].createdAt = new Date()
user.userRoles[0].role = role
user.userRoles[0].userId = user.id
await UserRole.save(user.userRoles[0])
}
}
export async function deleteUserRole(user: DbUser): Promise<void> {
if (user.userRoles.length > 0) {
// remove all roles of the user
await UserRole.delete({ userId: user.id })
user.userRoles.length = 0
} else if (user.userRoles.length === 0) {
throw new LogError('User is already an usual user')
}
}

View File

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/unbound-method */
import { User } from '@entity/User'
import { UserRole } from '@entity/UserRole'
import { ApolloServerTestClient } from 'apollo-server-testing'
import { ROLE_NAMES } from '@/auth/ROLES'
import { setUserRole } from '@/graphql/resolver/util/modifyUserRole'
import { createUser, setPassword } from '@/seeds/graphql/mutations'
import { UserInterface } from '@/seeds/users/UserInterface'
@ -19,13 +19,10 @@ export const userFactory = async (
createUser: { id },
},
} = await mutate({ mutation: createUser, variables: user })
// console.log('after creatUser:', { id }, { user })
// get user from database
let dbUser = await User.findOneOrFail({ where: { id }, relations: ['emailContact', 'userRoles'] })
// console.log('dbUser:', dbUser)
const emailContact = dbUser.emailContact
// console.log('emailContact:', emailContact)
if (user.emailChecked) {
await mutate({
@ -44,12 +41,7 @@ export const userFactory = async (
user.role &&
(user.role === ROLE_NAMES.ROLE_NAME_ADMIN || user.role === ROLE_NAMES.ROLE_NAME_MODERATOR)
) {
dbUser.userRoles = [] as UserRole[]
dbUser.userRoles[0] = UserRole.create()
dbUser.userRoles[0].createdAt = new Date()
dbUser.userRoles[0].role = user.role
dbUser.userRoles[0].userId = dbUser.id
await dbUser.userRoles[0].save()
await setUserRole(dbUser, user.role)
}
await dbUser.save()
}

View File

@ -26,7 +26,7 @@ const communityDbUser: dbUser = {
createdAt: new Date(),
// emailChecked: false,
language: '',
userRoles: undefined,
userRoles: [],
publisherId: 0,
// default password encryption type
passwordEncryptionType: PasswordEncryptionType.NO_PASSWORD,

View File

@ -89,7 +89,7 @@ export class User extends BaseEntity {
@OneToMany(() => UserRole, (userRole) => userRole.user)
@JoinColumn({ name: 'user_id' })
userRoles?: UserRole[]
userRoles: UserRole[]
@Column({ name: 'referrer_id', type: 'int', unsigned: true, nullable: true, default: null })
referrerId?: number | null

View File

@ -18,7 +18,7 @@ export class UserRole extends BaseEntity {
@Column({ name: 'updated_at', nullable: true, default: null, type: 'datetime' })
updatedAt: Date | null
@ManyToOne(() => User, (user) => user.userRoles, { nullable: true })
@ManyToOne(() => User, (user) => user.userRoles)
@JoinColumn({ name: 'user_id' })
user: User | null
user: User
}

View File

@ -0,0 +1,12 @@
const report = require("multiple-cucumber-html-reporter");
const reportTitle = "Gradido webapp end-to-end test report"
report.generate({
jsonDir: "cypress/reports/json_logs",
reportPath: "./cypress/reports/cucumber_html_report",
pageTitle: reportTitle,
reportName: reportTitle,
pageFooter: "<div></div>",
hideMetadata: true
});

View File

@ -26,14 +26,6 @@ async function setupNodeEvents(
},
})
on('after:run', (results) => {
if (results) {
// results will be undefined in interactive mode
// eslint-disable-next-line no-console
console.log(results.status)
}
})
return config
}

View File

@ -8,9 +8,18 @@
"license": "Apache-2.0",
"private": false,
"cypress-cucumber-preprocessor": {
"nonGlobalStepDefinitions": true,
"stepDefinitions": "cypress/support/step_definitions/*.ts",
"json": {
"enabled": true
"enabled": true,
"output": "cypress/reports/json_logs/cucumber_log.json",
"formatter": "cucumber-json-formatter"
},
"messages": {
"enabled": true,
"output": "cypress/reports/json_logs/messages.ndjson"
},
"html": {
"enabled": false
}
},
"scripts": {
@ -33,6 +42,7 @@
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-promise": "^6.1.1",
"jwt-decode": "^3.1.2",
"multiple-cucumber-html-reporter": "^3.4.0",
"prettier": "^2.7.1",
"typescript": "^4.7.4"
}

File diff suppressed because it is too large Load Diff