mirror of
https://github.com/IT4Change/gradido.git
synced 2026-02-06 09:56:05 +00:00
Merge branch '3030-feature-role-administration-backend' of github.com:gradido/gradido into 3030-feature-role-administration-backend
This commit is contained in:
commit
736c9228a6
30
.github/workflows/test_e2e.yml
vendored
30
.github/workflows/test_e2e.yml
vendored
@ -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
|
||||
|
||||
@ -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')
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
30
backend/src/graphql/resolver/util/modifyUserRole.ts
Normal file
30
backend/src/graphql/resolver/util/modifyUserRole.ts
Normal 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')
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
12
e2e-tests/create-cucumber-html-report.js
Normal file
12
e2e-tests/create-cucumber-html-report.js
Normal 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
|
||||
});
|
||||
@ -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
|
||||
}
|
||||
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
2529
e2e-tests/yarn.lock
2529
e2e-tests/yarn.lock
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user