mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-19 01:17:18 +02:00
refactor: add schemas to user admin controller (#1692)
* refactor: add schemas to user admin controller * refactor: remove unused SessionService * refactor: fix search query type confusion * refactor: add schemas to user controller (#1693) * refactor: add schemas to user controller * refactor: fix getAllUserSplashes method name * refactor: name and email should not be required on create * refactor: only some user fields may be updated * refactor: should not require any fields on user update (#1730) * refactor: send 400 instead of 500 on missing username and email * refactor: should not require any fields for user update * refactor: note that earlier versions required name or email * refactor: merge roleDescriptionSchema and roleSchema
This commit is contained in:
parent
cecca59f65
commit
ab75d4085e
@ -38,7 +38,7 @@ export default class UserSplashStore implements IUserSplashStore {
|
|||||||
this.logger = getLogger('user-splash-store.ts');
|
this.logger = getLogger('user-splash-store.ts');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUserSplashs(userId: number): Promise<IUserSplash[]> {
|
async getAllUserSplashes(userId: number): Promise<IUserSplash[]> {
|
||||||
const userSplash = await this.db
|
const userSplash = await this.db
|
||||||
.table<IUserSplashTable>(TABLE)
|
.table<IUserSplashTable>(TABLE)
|
||||||
.select()
|
.select()
|
||||||
|
@ -8,7 +8,6 @@ import NotFoundError from '../error/notfound-error';
|
|||||||
import {
|
import {
|
||||||
ICreateUser,
|
ICreateUser,
|
||||||
IUserLookup,
|
IUserLookup,
|
||||||
IUserSearch,
|
|
||||||
IUserStore,
|
IUserStore,
|
||||||
IUserUpdateFields,
|
IUserUpdateFields,
|
||||||
} from '../types/stores/user-store';
|
} from '../types/stores/user-store';
|
||||||
@ -116,7 +115,7 @@ class UserStore implements IUserStore {
|
|||||||
return users.map(rowToUser);
|
return users.map(rowToUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query: IUserSearch): Promise<User[]> {
|
async search(query: string): Promise<User[]> {
|
||||||
const users = await this.db
|
const users = await this.db
|
||||||
.select(USER_COLUMNS_PUBLIC)
|
.select(USER_COLUMNS_PUBLIC)
|
||||||
.from(TABLE)
|
.from(TABLE)
|
||||||
|
@ -8,6 +8,7 @@ import { contextFieldsSchema } from './spec/context-fields-schema';
|
|||||||
import { createApiTokenSchema } from './spec/create-api-token-schema';
|
import { createApiTokenSchema } from './spec/create-api-token-schema';
|
||||||
import { createFeatureSchema } from './spec/create-feature-schema';
|
import { createFeatureSchema } from './spec/create-feature-schema';
|
||||||
import { createStrategySchema } from './spec/create-strategy-schema';
|
import { createStrategySchema } from './spec/create-strategy-schema';
|
||||||
|
import { createUserSchema } from './spec/create-user-schema';
|
||||||
import { environmentSchema } from './spec/environment-schema';
|
import { environmentSchema } from './spec/environment-schema';
|
||||||
import { environmentsSchema } from './spec/environments-schema';
|
import { environmentsSchema } from './spec/environments-schema';
|
||||||
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
|
import { featureEnvironmentSchema } from './spec/feature-environment-schema';
|
||||||
@ -22,16 +23,21 @@ import { healthCheckSchema } from './spec/health-check-schema';
|
|||||||
import { healthOverviewSchema } from './spec/health-overview-schema';
|
import { healthOverviewSchema } from './spec/health-overview-schema';
|
||||||
import { healthReportSchema } from './spec/health-report-schema';
|
import { healthReportSchema } from './spec/health-report-schema';
|
||||||
import { legalValueSchema } from './spec/legal-value-schema';
|
import { legalValueSchema } from './spec/legal-value-schema';
|
||||||
|
import { idSchema } from './spec/id-schema';
|
||||||
import { mapValues } from '../util/map-values';
|
import { mapValues } from '../util/map-values';
|
||||||
import { nameSchema } from './spec/name-schema';
|
import { nameSchema } from './spec/name-schema';
|
||||||
|
import { meSchema } from './spec/me-schema';
|
||||||
import { omitKeys } from '../util/omit-keys';
|
import { omitKeys } from '../util/omit-keys';
|
||||||
import { overrideSchema } from './spec/override-schema';
|
import { overrideSchema } from './spec/override-schema';
|
||||||
import { parametersSchema } from './spec/parameters-schema';
|
import { parametersSchema } from './spec/parameters-schema';
|
||||||
|
import { passwordSchema } from './spec/password-schema';
|
||||||
import { patchSchema } from './spec/patch-schema';
|
import { patchSchema } from './spec/patch-schema';
|
||||||
import { patchesSchema } from './spec/patches-schema';
|
import { patchesSchema } from './spec/patches-schema';
|
||||||
|
import { permissionSchema } from './spec/permission-schema';
|
||||||
import { projectEnvironmentSchema } from './spec/project-environment-schema';
|
import { projectEnvironmentSchema } from './spec/project-environment-schema';
|
||||||
import { projectSchema } from './spec/project-schema';
|
import { projectSchema } from './spec/project-schema';
|
||||||
import { projectsSchema } from './spec/projects-schema';
|
import { projectsSchema } from './spec/projects-schema';
|
||||||
|
import { roleSchema } from './spec/role-schema';
|
||||||
import { sortOrderSchema } from './spec/sort-order-schema';
|
import { sortOrderSchema } from './spec/sort-order-schema';
|
||||||
import { splashSchema } from './spec/splash-schema';
|
import { splashSchema } from './spec/splash-schema';
|
||||||
import { strategySchema } from './spec/strategy-schema';
|
import { strategySchema } from './spec/strategy-schema';
|
||||||
@ -45,6 +51,10 @@ import { updateStrategySchema } from './spec/update-strategy-schema';
|
|||||||
import { updateApiTokenSchema } from './spec/update-api-token-schema';
|
import { updateApiTokenSchema } from './spec/update-api-token-schema';
|
||||||
import { updateTagTypeSchema } from './spec/update-tag-type-schema';
|
import { updateTagTypeSchema } from './spec/update-tag-type-schema';
|
||||||
import { upsertContextFieldSchema } from './spec/upsert-context-field-schema';
|
import { upsertContextFieldSchema } from './spec/upsert-context-field-schema';
|
||||||
|
import { updateUserSchema } from './spec/update-user-schema';
|
||||||
|
import { userSchema } from './spec/user-schema';
|
||||||
|
import { usersSchema } from './spec/users-schema';
|
||||||
|
import { usersSearchSchema } from './spec/users-search-schema';
|
||||||
import { validateTagTypeSchema } from './spec/validate-tag-type-schema';
|
import { validateTagTypeSchema } from './spec/validate-tag-type-schema';
|
||||||
import { variantSchema } from './spec/variant-schema';
|
import { variantSchema } from './spec/variant-schema';
|
||||||
import { variantsSchema } from './spec/variants-schema';
|
import { variantsSchema } from './spec/variants-schema';
|
||||||
@ -57,7 +67,6 @@ import { applicationSchema } from './spec/application-schema';
|
|||||||
import { applicationsSchema } from './spec/applications-schema';
|
import { applicationsSchema } from './spec/applications-schema';
|
||||||
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
|
import { tagWithVersionSchema } from './spec/tag-with-version-schema';
|
||||||
import { tokenUserSchema } from './spec/token-user-schema';
|
import { tokenUserSchema } from './spec/token-user-schema';
|
||||||
import { roleDescriptionSchema } from './spec/role-description-schema';
|
|
||||||
import { changePasswordSchema } from './spec/change-password-schema';
|
import { changePasswordSchema } from './spec/change-password-schema';
|
||||||
import { validatePasswordSchema } from './spec/validate-password-schema';
|
import { validatePasswordSchema } from './spec/validate-password-schema';
|
||||||
import { resetPasswordSchema } from './spec/reset-password-schema';
|
import { resetPasswordSchema } from './spec/reset-password-schema';
|
||||||
@ -66,6 +75,7 @@ import { segmentSchema } from './spec/segment-schema';
|
|||||||
import { stateSchema } from './spec/state-schema';
|
import { stateSchema } from './spec/state-schema';
|
||||||
import { featureTagSchema } from './spec/feature-tag-schema';
|
import { featureTagSchema } from './spec/feature-tag-schema';
|
||||||
import { exportParametersSchema } from './spec/export-parameters-schema';
|
import { exportParametersSchema } from './spec/export-parameters-schema';
|
||||||
|
import { emailSchema } from './spec/email-schema';
|
||||||
|
|
||||||
// All schemas in `openapi/spec` should be listed here.
|
// All schemas in `openapi/spec` should be listed here.
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
@ -85,6 +95,8 @@ export const schemas = {
|
|||||||
createApiTokenSchema,
|
createApiTokenSchema,
|
||||||
createFeatureSchema,
|
createFeatureSchema,
|
||||||
createStrategySchema,
|
createStrategySchema,
|
||||||
|
createUserSchema,
|
||||||
|
emailSchema,
|
||||||
environmentSchema,
|
environmentSchema,
|
||||||
environmentsSchema,
|
environmentsSchema,
|
||||||
exportParametersSchema,
|
exportParametersSchema,
|
||||||
@ -103,15 +115,19 @@ export const schemas = {
|
|||||||
healthReportSchema,
|
healthReportSchema,
|
||||||
legalValueSchema,
|
legalValueSchema,
|
||||||
nameSchema,
|
nameSchema,
|
||||||
|
idSchema,
|
||||||
|
meSchema,
|
||||||
overrideSchema,
|
overrideSchema,
|
||||||
parametersSchema,
|
parametersSchema,
|
||||||
|
passwordSchema,
|
||||||
patchSchema,
|
patchSchema,
|
||||||
patchesSchema,
|
patchesSchema,
|
||||||
|
permissionSchema,
|
||||||
projectEnvironmentSchema,
|
projectEnvironmentSchema,
|
||||||
projectSchema,
|
projectSchema,
|
||||||
projectsSchema,
|
projectsSchema,
|
||||||
resetPasswordSchema,
|
resetPasswordSchema,
|
||||||
roleDescriptionSchema,
|
roleSchema,
|
||||||
segmentSchema,
|
segmentSchema,
|
||||||
sortOrderSchema,
|
sortOrderSchema,
|
||||||
splashSchema,
|
splashSchema,
|
||||||
@ -131,6 +147,10 @@ export const schemas = {
|
|||||||
upsertContextFieldSchema,
|
upsertContextFieldSchema,
|
||||||
validatePasswordSchema,
|
validatePasswordSchema,
|
||||||
validateTagTypeSchema,
|
validateTagTypeSchema,
|
||||||
|
updateUserSchema,
|
||||||
|
userSchema,
|
||||||
|
usersSchema,
|
||||||
|
usersSearchSchema,
|
||||||
variantSchema,
|
variantSchema,
|
||||||
variantsSchema,
|
variantsSchema,
|
||||||
versionSchema,
|
versionSchema,
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`resetPasswordSchema empty 1`] = `
|
exports[`emailSchema 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {},
|
"data": Object {},
|
||||||
"errors": Array [
|
"errors": Array [
|
||||||
@ -14,6 +14,6 @@ Object {
|
|||||||
"schemaPath": "#/required",
|
"schemaPath": "#/required",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"schema": "#/components/schemas/resetPasswordSchema",
|
"schema": "#/components/schemas/emailSchema",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
65
src/lib/openapi/spec/__snapshots__/me-schema.test.ts.snap
Normal file
65
src/lib/openapi/spec/__snapshots__/me-schema.test.ts.snap
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`meSchema empty 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {},
|
||||||
|
"errors": Array [
|
||||||
|
Object {
|
||||||
|
"instancePath": "",
|
||||||
|
"keyword": "required",
|
||||||
|
"message": "must have required property 'user'",
|
||||||
|
"params": Object {
|
||||||
|
"missingProperty": "user",
|
||||||
|
},
|
||||||
|
"schemaPath": "#/required",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"schema": "#/components/schemas/meSchema",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`meSchema missing permissions 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {
|
||||||
|
"user": Object {
|
||||||
|
"id": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"errors": Array [
|
||||||
|
Object {
|
||||||
|
"instancePath": "",
|
||||||
|
"keyword": "required",
|
||||||
|
"message": "must have required property 'permissions'",
|
||||||
|
"params": Object {
|
||||||
|
"missingProperty": "permissions",
|
||||||
|
},
|
||||||
|
"schemaPath": "#/required",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"schema": "#/components/schemas/meSchema",
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`meSchema missing splash 1`] = `
|
||||||
|
Object {
|
||||||
|
"data": Object {
|
||||||
|
"feedback": Array [],
|
||||||
|
"permissions": Array [],
|
||||||
|
"user": Object {
|
||||||
|
"id": 1,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"errors": Array [
|
||||||
|
Object {
|
||||||
|
"instancePath": "",
|
||||||
|
"keyword": "required",
|
||||||
|
"message": "must have required property 'splash'",
|
||||||
|
"params": Object {
|
||||||
|
"missingProperty": "splash",
|
||||||
|
},
|
||||||
|
"schemaPath": "#/required",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"schema": "#/components/schemas/meSchema",
|
||||||
|
}
|
||||||
|
`;
|
@ -1,19 +1,19 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
exports[`roleDescriptionSchema empty 1`] = `
|
exports[`roleSchema 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"data": Object {},
|
"data": Object {},
|
||||||
"errors": Array [
|
"errors": Array [
|
||||||
Object {
|
Object {
|
||||||
"instancePath": "",
|
"instancePath": "",
|
||||||
"keyword": "required",
|
"keyword": "required",
|
||||||
"message": "must have required property 'description'",
|
"message": "must have required property 'id'",
|
||||||
"params": Object {
|
"params": Object {
|
||||||
"missingProperty": "description",
|
"missingProperty": "id",
|
||||||
},
|
},
|
||||||
"schemaPath": "#/required",
|
"schemaPath": "#/required",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"schema": "#/components/schemas/roleDescriptionSchema",
|
"schema": "#/components/schemas/roleSchema",
|
||||||
}
|
}
|
||||||
`;
|
`;
|
31
src/lib/openapi/spec/create-user-schema.ts
Normal file
31
src/lib/openapi/spec/create-user-schema.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const createUserSchema = {
|
||||||
|
$id: '#/components/schemas/createUserSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['rootRole'],
|
||||||
|
properties: {
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
rootRole: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
sendEmail: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type CreateUserSchema = FromSchema<typeof createUserSchema>;
|
16
src/lib/openapi/spec/email-schema.test.ts
Normal file
16
src/lib/openapi/spec/email-schema.test.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { EmailSchema } from './email-schema';
|
||||||
|
|
||||||
|
test('emailSchema', () => {
|
||||||
|
const data: EmailSchema = {
|
||||||
|
email: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/emailSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/emailSchema', {}),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
16
src/lib/openapi/spec/email-schema.ts
Normal file
16
src/lib/openapi/spec/email-schema.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const emailSchema = {
|
||||||
|
$id: '#/components/schemas/emailSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['email'],
|
||||||
|
properties: {
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type EmailSchema = FromSchema<typeof emailSchema>;
|
16
src/lib/openapi/spec/id-schema.ts
Normal file
16
src/lib/openapi/spec/id-schema.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const idSchema = {
|
||||||
|
$id: '#/components/schemas/idSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['id'],
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type IdSchema = FromSchema<typeof idSchema>;
|
37
src/lib/openapi/spec/me-schema.test.ts
Normal file
37
src/lib/openapi/spec/me-schema.test.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { MeSchema } from './me-schema';
|
||||||
|
|
||||||
|
test('meSchema', () => {
|
||||||
|
const data: MeSchema = {
|
||||||
|
user: { id: 1 },
|
||||||
|
permissions: [{ permission: 'a' }],
|
||||||
|
feedback: [{ userId: 1, feedbackId: 'a', neverShow: false }],
|
||||||
|
splash: { a: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/meSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('meSchema empty', () => {
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/meSchema', {}),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('meSchema missing permissions', () => {
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/meSchema', { user: { id: 1 } }),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('meSchema missing splash', () => {
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/meSchema', {
|
||||||
|
user: { id: 1 },
|
||||||
|
permissions: [],
|
||||||
|
feedback: [],
|
||||||
|
}),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
43
src/lib/openapi/spec/me-schema.ts
Normal file
43
src/lib/openapi/spec/me-schema.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { userSchema } from './user-schema';
|
||||||
|
import { permissionSchema } from './permission-schema';
|
||||||
|
import { feedbackSchema } from './feedback-schema';
|
||||||
|
|
||||||
|
export const meSchema = {
|
||||||
|
$id: '#/components/schemas/meSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['user', 'permissions', 'feedback', 'splash'],
|
||||||
|
properties: {
|
||||||
|
user: {
|
||||||
|
$ref: '#/components/schemas/userSchema',
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/permissionSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
feedback: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/feedbackSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
splash: {
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
userSchema,
|
||||||
|
permissionSchema,
|
||||||
|
feedbackSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type MeSchema = FromSchema<typeof meSchema>;
|
19
src/lib/openapi/spec/password-schema.ts
Normal file
19
src/lib/openapi/spec/password-schema.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const passwordSchema = {
|
||||||
|
$id: '#/components/schemas/passwordSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['password'],
|
||||||
|
properties: {
|
||||||
|
password: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
confirmPassword: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PasswordSchema = FromSchema<typeof passwordSchema>;
|
22
src/lib/openapi/spec/permission-schema.ts
Normal file
22
src/lib/openapi/spec/permission-schema.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const permissionSchema = {
|
||||||
|
$id: '#/components/schemas/permissionSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['permission'],
|
||||||
|
properties: {
|
||||||
|
permission: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
project: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
environment: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type PermissionSchema = FromSchema<typeof permissionSchema>;
|
@ -1,18 +0,0 @@
|
|||||||
import { validateSchema } from '../validate';
|
|
||||||
import { ResetPasswordSchema } from './reset-password-schema';
|
|
||||||
|
|
||||||
test('resetPasswordSchema', () => {
|
|
||||||
const data: ResetPasswordSchema = {
|
|
||||||
email: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(
|
|
||||||
validateSchema('#/components/schemas/resetPasswordSchema', data),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('resetPasswordSchema empty', () => {
|
|
||||||
expect(
|
|
||||||
validateSchema('#/components/schemas/resetPasswordSchema', {}),
|
|
||||||
).toMatchSnapshot();
|
|
||||||
});
|
|
@ -4,9 +4,9 @@ export const resetPasswordSchema = {
|
|||||||
$id: '#/components/schemas/resetPasswordSchema',
|
$id: '#/components/schemas/resetPasswordSchema',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: ['email'],
|
required: ['resetPasswordUrl'],
|
||||||
properties: {
|
properties: {
|
||||||
email: {
|
resetPasswordUrl: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1,20 +0,0 @@
|
|||||||
import { validateSchema } from '../validate';
|
|
||||||
import { RoleDescriptionSchema } from './role-description-schema';
|
|
||||||
|
|
||||||
test('roleDescriptionSchema', () => {
|
|
||||||
const data: RoleDescriptionSchema = {
|
|
||||||
description: '',
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(
|
|
||||||
validateSchema('#/components/schemas/roleDescriptionSchema', data),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('roleDescriptionSchema empty', () => {
|
|
||||||
expect(
|
|
||||||
validateSchema('#/components/schemas/roleDescriptionSchema', {}),
|
|
||||||
).toMatchSnapshot();
|
|
||||||
});
|
|
19
src/lib/openapi/spec/role-schema.test.ts
Normal file
19
src/lib/openapi/spec/role-schema.test.ts
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { RoleSchema } from './role-schema';
|
||||||
|
|
||||||
|
test('roleSchema', () => {
|
||||||
|
const data: RoleSchema = {
|
||||||
|
id: 1,
|
||||||
|
description: '',
|
||||||
|
name: '',
|
||||||
|
type: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/roleSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/roleSchema', {}),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
});
|
@ -1,22 +1,25 @@
|
|||||||
import { FromSchema } from 'json-schema-to-ts';
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
export const roleDescriptionSchema = {
|
export const roleSchema = {
|
||||||
$id: '#/components/schemas/roleDescriptionSchema',
|
$id: '#/components/schemas/roleSchema',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
required: ['description', 'name', 'type'],
|
required: ['id', 'type', 'name'],
|
||||||
properties: {
|
properties: {
|
||||||
description: {
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
type: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
type: {
|
description: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {},
|
components: {},
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
export type RoleDescriptionSchema = FromSchema<typeof roleDescriptionSchema>;
|
export type RoleSchema = FromSchema<typeof roleSchema>;
|
@ -6,6 +6,7 @@ test('tokenUserSchema', () => {
|
|||||||
createdBy: '',
|
createdBy: '',
|
||||||
token: '',
|
token: '',
|
||||||
role: {
|
role: {
|
||||||
|
id: 1,
|
||||||
description: '',
|
description: '',
|
||||||
name: '',
|
name: '',
|
||||||
type: '',
|
type: '',
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { FromSchema } from 'json-schema-to-ts';
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
import { roleDescriptionSchema } from './role-description-schema';
|
import { roleSchema } from './role-schema';
|
||||||
|
|
||||||
export const tokenUserSchema = {
|
export const tokenUserSchema = {
|
||||||
$id: '#/components/schemas/tokenUserSchema',
|
$id: '#/components/schemas/tokenUserSchema',
|
||||||
@ -14,12 +14,12 @@ export const tokenUserSchema = {
|
|||||||
type: 'string',
|
type: 'string',
|
||||||
},
|
},
|
||||||
role: {
|
role: {
|
||||||
$ref: '#/components/schemas/roleDescriptionSchema',
|
$ref: '#/components/schemas/roleSchema',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
schemas: {
|
schemas: {
|
||||||
roleDescriptionSchema,
|
roleSchema,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
21
src/lib/openapi/spec/update-user-schema.ts
Normal file
21
src/lib/openapi/spec/update-user-schema.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const updateUserSchema = {
|
||||||
|
$id: '#/components/schemas/updateUserSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
properties: {
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
rootRole: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UpdateUserSchema = FromSchema<typeof updateUserSchema>;
|
52
src/lib/openapi/spec/user-schema.ts
Normal file
52
src/lib/openapi/spec/user-schema.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
|
||||||
|
export const userSchema = {
|
||||||
|
$id: '#/components/schemas/userSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['id'],
|
||||||
|
properties: {
|
||||||
|
id: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
isAPI: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
username: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
imageUrl: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
inviteLink: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
loginAttempts: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
emailSent: {
|
||||||
|
type: 'boolean',
|
||||||
|
},
|
||||||
|
rootRole: {
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
seenAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
nullable: true,
|
||||||
|
},
|
||||||
|
createdAt: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'date-time',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UserSchema = FromSchema<typeof userSchema>;
|
13
src/lib/openapi/spec/users-schema.test.ts
Normal file
13
src/lib/openapi/spec/users-schema.test.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { UsersSchema } from './users-schema';
|
||||||
|
|
||||||
|
test('usersSchema', () => {
|
||||||
|
const data: UsersSchema = {
|
||||||
|
users: [{ id: 1 }],
|
||||||
|
rootRoles: [{ id: 1, type: 'a', name: 'b' }],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/usersSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
32
src/lib/openapi/spec/users-schema.ts
Normal file
32
src/lib/openapi/spec/users-schema.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { userSchema } from './user-schema';
|
||||||
|
import { roleSchema } from './role-schema';
|
||||||
|
|
||||||
|
export const usersSchema = {
|
||||||
|
$id: '#/components/schemas/usersSchema',
|
||||||
|
type: 'object',
|
||||||
|
additionalProperties: false,
|
||||||
|
required: ['users'],
|
||||||
|
properties: {
|
||||||
|
users: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/userSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
rootRoles: {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/roleSchema',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
userSchema,
|
||||||
|
roleSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UsersSchema = FromSchema<typeof usersSchema>;
|
10
src/lib/openapi/spec/users-search-schema.test.ts
Normal file
10
src/lib/openapi/spec/users-search-schema.test.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { validateSchema } from '../validate';
|
||||||
|
import { UsersSearchSchema } from './users-search-schema';
|
||||||
|
|
||||||
|
test('usersSchema', () => {
|
||||||
|
const data: UsersSearchSchema = [{ id: 1 }];
|
||||||
|
|
||||||
|
expect(
|
||||||
|
validateSchema('#/components/schemas/usersSearchSchema', data),
|
||||||
|
).toBeUndefined();
|
||||||
|
});
|
17
src/lib/openapi/spec/users-search-schema.ts
Normal file
17
src/lib/openapi/spec/users-search-schema.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { FromSchema } from 'json-schema-to-ts';
|
||||||
|
import { userSchema } from './user-schema';
|
||||||
|
|
||||||
|
export const usersSearchSchema = {
|
||||||
|
$id: '#/components/schemas/usersSearchSchema',
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
$ref: '#/components/schemas/userSchema',
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
schemas: {
|
||||||
|
userSchema,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type UsersSearchSchema = FromSchema<typeof usersSearchSchema>;
|
@ -8,20 +8,29 @@ import { IUnleashConfig } from '../../types/option';
|
|||||||
import { EmailService } from '../../services/email-service';
|
import { EmailService } from '../../services/email-service';
|
||||||
import ResetTokenService from '../../services/reset-token-service';
|
import ResetTokenService from '../../services/reset-token-service';
|
||||||
import { IUnleashServices } from '../../types/services';
|
import { IUnleashServices } from '../../types/services';
|
||||||
import SessionService from '../../services/session-service';
|
|
||||||
import { IAuthRequest } from '../unleash-types';
|
import { IAuthRequest } from '../unleash-types';
|
||||||
import SettingService from '../../services/setting-service';
|
import SettingService from '../../services/setting-service';
|
||||||
import { IUser, SimpleAuthSettings } from '../../server-impl';
|
import { IUser, SimpleAuthSettings } from '../../server-impl';
|
||||||
import { simpleAuthKey } from '../../types/settings/simple-auth-settings';
|
import { simpleAuthKey } from '../../types/settings/simple-auth-settings';
|
||||||
import { anonymise } from '../../util/anonymise';
|
import { anonymise } from '../../util/anonymise';
|
||||||
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
interface ICreateUserBody {
|
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||||
username: string;
|
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||||
email: string;
|
import { userSchema, UserSchema } from '../../openapi/spec/user-schema';
|
||||||
name: string;
|
import { serializeDates } from '../../types/serialize-dates';
|
||||||
rootRole: number;
|
import { usersSchema, UsersSchema } from '../../openapi/spec/users-schema';
|
||||||
sendEmail: boolean;
|
import {
|
||||||
}
|
usersSearchSchema,
|
||||||
|
UsersSearchSchema,
|
||||||
|
} from '../../openapi/spec/users-search-schema';
|
||||||
|
import { CreateUserSchema } from '../../openapi/spec/create-user-schema';
|
||||||
|
import { UpdateUserSchema } from '../../openapi/spec/update-user-schema';
|
||||||
|
import { PasswordSchema } from '../../openapi/spec/password-schema';
|
||||||
|
import { IdSchema } from '../../openapi/spec/id-schema';
|
||||||
|
import {
|
||||||
|
resetPasswordSchema,
|
||||||
|
ResetPasswordSchema,
|
||||||
|
} from '../../openapi/spec/reset-password-schema';
|
||||||
|
|
||||||
export default class UserAdminController extends Controller {
|
export default class UserAdminController extends Controller {
|
||||||
private anonymise: boolean = false;
|
private anonymise: boolean = false;
|
||||||
@ -36,10 +45,10 @@ export default class UserAdminController extends Controller {
|
|||||||
|
|
||||||
private resetTokenService: ResetTokenService;
|
private resetTokenService: ResetTokenService;
|
||||||
|
|
||||||
private sessionService: SessionService;
|
|
||||||
|
|
||||||
private settingService: SettingService;
|
private settingService: SettingService;
|
||||||
|
|
||||||
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
readonly unleashUrl: string;
|
readonly unleashUrl: string;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ -49,16 +58,16 @@ export default class UserAdminController extends Controller {
|
|||||||
accessService,
|
accessService,
|
||||||
emailService,
|
emailService,
|
||||||
resetTokenService,
|
resetTokenService,
|
||||||
sessionService,
|
|
||||||
settingService,
|
settingService,
|
||||||
|
openApiService,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
| 'userService'
|
| 'userService'
|
||||||
| 'accessService'
|
| 'accessService'
|
||||||
| 'emailService'
|
| 'emailService'
|
||||||
| 'resetTokenService'
|
| 'resetTokenService'
|
||||||
| 'sessionService'
|
|
||||||
| 'settingService'
|
| 'settingService'
|
||||||
|
| 'openApiService'
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
@ -66,32 +75,165 @@ export default class UserAdminController extends Controller {
|
|||||||
this.accessService = accessService;
|
this.accessService = accessService;
|
||||||
this.emailService = emailService;
|
this.emailService = emailService;
|
||||||
this.resetTokenService = resetTokenService;
|
this.resetTokenService = resetTokenService;
|
||||||
this.sessionService = sessionService;
|
|
||||||
this.settingService = settingService;
|
this.settingService = settingService;
|
||||||
|
this.openApiService = openApiService;
|
||||||
this.logger = config.getLogger('routes/user-controller.ts');
|
this.logger = config.getLogger('routes/user-controller.ts');
|
||||||
this.unleashUrl = config.server.unleashUrl;
|
this.unleashUrl = config.server.unleashUrl;
|
||||||
this.anonymise = config.experimental?.anonymiseEventLog;
|
this.anonymise = config.experimental?.anonymiseEventLog;
|
||||||
|
|
||||||
this.get('/', this.getUsers, ADMIN);
|
this.route({
|
||||||
this.get('/search', this.search);
|
method: 'post',
|
||||||
this.post('/', this.createUser, ADMIN);
|
path: '/validate-password',
|
||||||
this.post('/validate-password', this.validatePassword, NONE);
|
handler: this.validatePassword,
|
||||||
this.get('/:id', this.getUser, ADMIN);
|
permission: NONE,
|
||||||
this.put('/:id', this.updateUser, ADMIN);
|
middleware: [
|
||||||
this.post('/:id/change-password', this.changePassword, ADMIN);
|
openApiService.validPath({
|
||||||
this.delete('/:id', this.deleteUser, ADMIN);
|
tags: ['admin'],
|
||||||
this.post('/reset-password', this.resetPassword, ADMIN);
|
operationId: 'validatePassword',
|
||||||
|
requestBody: createRequestSchema('passwordSchema'),
|
||||||
|
responses: { 200: emptyResponse },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '/:id/change-password',
|
||||||
|
handler: this.changePassword,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'changePassword',
|
||||||
|
requestBody: createRequestSchema('passwordSchema'),
|
||||||
|
responses: { 200: emptyResponse },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '/reset-password',
|
||||||
|
handler: this.resetPassword,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'resetPassword',
|
||||||
|
requestBody: createRequestSchema('idSchema'),
|
||||||
|
responses: {
|
||||||
|
200: createResponseSchema('resetPasswordSchema'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '',
|
||||||
|
handler: this.getUsers,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'getUsers',
|
||||||
|
responses: { 200: createResponseSchema('usersSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '/search',
|
||||||
|
handler: this.searchUsers,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'searchUsers',
|
||||||
|
responses: { 200: createResponseSchema('usersSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '',
|
||||||
|
handler: this.createUser,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'createUser',
|
||||||
|
requestBody: createRequestSchema('createUserSchema'),
|
||||||
|
responses: { 200: createResponseSchema('userSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'get',
|
||||||
|
path: '/:id',
|
||||||
|
handler: this.getUser,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'getUser',
|
||||||
|
responses: { 200: createResponseSchema('userSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'put',
|
||||||
|
path: '/:id',
|
||||||
|
handler: this.updateUser,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'updateUser',
|
||||||
|
requestBody: createRequestSchema('updateUserSchema'),
|
||||||
|
responses: { 200: createResponseSchema('userSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'delete',
|
||||||
|
path: '/:id',
|
||||||
|
acceptAnyContentType: true,
|
||||||
|
handler: this.deleteUser,
|
||||||
|
permission: ADMIN,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'deleteUser',
|
||||||
|
responses: { 200: emptyResponse },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async resetPassword(req: IAuthRequest, res: Response): Promise<void> {
|
async resetPassword(
|
||||||
|
req: IAuthRequest<unknown, ResetPasswordSchema, IdSchema>,
|
||||||
|
res: Response<ResetPasswordSchema>,
|
||||||
|
): Promise<void> {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
const receiver = req.body.id;
|
const receiver = req.body.id;
|
||||||
const resetPasswordUrl =
|
const resetPasswordUrl =
|
||||||
await this.userService.createResetPasswordEmail(receiver, user);
|
await this.userService.createResetPasswordEmail(receiver, user);
|
||||||
res.json({ resetPasswordUrl });
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
resetPasswordSchema.$id,
|
||||||
|
{ resetPasswordUrl: resetPasswordUrl.toString() },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUsers(req: Request, res: Response): Promise<void> {
|
async getUsers(req: Request, res: Response<UsersSchema>): Promise<void> {
|
||||||
const users = await this.userService.getAll();
|
const users = await this.userService.getAll();
|
||||||
const rootRoles = await this.accessService.getRootRoles();
|
const rootRoles = await this.accessService.getRootRoles();
|
||||||
const inviteLinks = await this.resetTokenService.getActiveInvitations();
|
const inviteLinks = await this.resetTokenService.getActiveInvitations();
|
||||||
@ -101,7 +243,10 @@ export default class UserAdminController extends Controller {
|
|||||||
return { ...user, inviteLink };
|
return { ...user, inviteLink };
|
||||||
});
|
});
|
||||||
|
|
||||||
res.json({ users: usersWithInviteLinks, rootRoles });
|
this.openApiService.respondWithValidation(200, res, usersSchema.$id, {
|
||||||
|
users: serializeDates(usersWithInviteLinks),
|
||||||
|
rootRoles,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
anonymiseUsers(users: IUser[]): IUser[] {
|
anonymiseUsers(users: IUser[]): IUser[] {
|
||||||
@ -113,36 +258,45 @@ export default class UserAdminController extends Controller {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(req: Request, res: Response): Promise<void> {
|
async searchUsers(
|
||||||
const { q } = req.query as any;
|
req: Request,
|
||||||
try {
|
res: Response<UsersSearchSchema>,
|
||||||
|
): Promise<void> {
|
||||||
|
const { q } = req.query;
|
||||||
let users =
|
let users =
|
||||||
q && q.length > 1 ? await this.userService.search(q) : [];
|
typeof q === 'string' && q.length > 1
|
||||||
|
? await this.userService.search(q)
|
||||||
|
: [];
|
||||||
if (this.anonymise) {
|
if (this.anonymise) {
|
||||||
users = this.anonymiseUsers(users);
|
users = this.anonymiseUsers(users);
|
||||||
}
|
}
|
||||||
res.json(users);
|
this.openApiService.respondWithValidation(
|
||||||
} catch (error) {
|
200,
|
||||||
this.logger.error(error);
|
res,
|
||||||
res.status(500).send({ msg: 'server errors' });
|
usersSearchSchema.$id,
|
||||||
}
|
serializeDates(users),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(req: Request, res: Response): Promise<void> {
|
async getUser(req: Request, res: Response<UserSchema>): Promise<void> {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const user = await this.userService.getUser(Number(id));
|
const user = await this.userService.getUser(Number(id));
|
||||||
res.json(user);
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
userSchema.$id,
|
||||||
|
serializeDates(user),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createUser(
|
async createUser(
|
||||||
req: IAuthRequest<any, any, ICreateUserBody, any>,
|
req: IAuthRequest<unknown, unknown, CreateUserSchema>,
|
||||||
res: Response,
|
res: Response<UserSchema>,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { username, email, name, rootRole, sendEmail } = req.body;
|
const { username, email, name, rootRole, sendEmail } = req.body;
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
|
|
||||||
try {
|
|
||||||
const createdUser = await this.userService.createUser(
|
const createdUser = await this.userService.createUser(
|
||||||
{
|
{
|
||||||
username,
|
username,
|
||||||
@ -154,9 +308,7 @@ export default class UserAdminController extends Controller {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const passwordAuthSettings =
|
const passwordAuthSettings =
|
||||||
await this.settingService.get<SimpleAuthSettings>(
|
await this.settingService.get<SimpleAuthSettings>(simpleAuthKey);
|
||||||
simpleAuthKey,
|
|
||||||
);
|
|
||||||
|
|
||||||
let inviteLink: string;
|
let inviteLink: string;
|
||||||
if (!passwordAuthSettings?.disabled) {
|
if (!passwordAuthSettings?.disabled) {
|
||||||
@ -171,6 +323,7 @@ export default class UserAdminController extends Controller {
|
|||||||
const emailConfigured = this.emailService.configured();
|
const emailConfigured = this.emailService.configured();
|
||||||
const reallySendEmail =
|
const reallySendEmail =
|
||||||
emailConfigured && (sendEmail !== undefined ? sendEmail : true);
|
emailConfigured && (sendEmail !== undefined ? sendEmail : true);
|
||||||
|
|
||||||
if (reallySendEmail) {
|
if (reallySendEmail) {
|
||||||
try {
|
try {
|
||||||
await this.emailService.sendGettingStartedMail(
|
await this.emailService.sendGettingStartedMail(
|
||||||
@ -192,25 +345,29 @@ export default class UserAdminController extends Controller {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
res.status(201).send({
|
const responseData: UserSchema = {
|
||||||
...createdUser,
|
...serializeDates(createdUser),
|
||||||
inviteLink: inviteLink || this.unleashUrl,
|
inviteLink: inviteLink || this.unleashUrl,
|
||||||
emailSent,
|
emailSent,
|
||||||
rootRole,
|
rootRole,
|
||||||
});
|
};
|
||||||
} catch (e) {
|
|
||||||
this.logger.warn(e.message);
|
this.openApiService.respondWithValidation(
|
||||||
res.status(400).send([{ msg: e.message }]);
|
201,
|
||||||
}
|
res,
|
||||||
|
userSchema.$id,
|
||||||
|
responseData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUser(req: IAuthRequest, res: Response): Promise<void> {
|
async updateUser(
|
||||||
|
req: IAuthRequest<{ id: string }, UserSchema, UpdateUserSchema>,
|
||||||
|
res: Response<UserSchema>,
|
||||||
|
): Promise<void> {
|
||||||
const { user, params, body } = req;
|
const { user, params, body } = req;
|
||||||
|
|
||||||
const { id } = params;
|
const { id } = params;
|
||||||
const { name, email, rootRole } = body;
|
const { name, email, rootRole } = body;
|
||||||
|
|
||||||
try {
|
|
||||||
const updateUser = await this.userService.updateUser(
|
const updateUser = await this.userService.updateUser(
|
||||||
{
|
{
|
||||||
id: Number(id),
|
id: Number(id),
|
||||||
@ -220,11 +377,11 @@ export default class UserAdminController extends Controller {
|
|||||||
},
|
},
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
res.status(200).send({ ...updateUser, rootRole });
|
|
||||||
} catch (e) {
|
this.openApiService.respondWithValidation(200, res, userSchema.$id, {
|
||||||
this.logger.warn(e.message);
|
...serializeDates(updateUser),
|
||||||
res.status(400).send([{ msg: e.message }]);
|
rootRole,
|
||||||
}
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteUser(req: IAuthRequest, res: Response): Promise<void> {
|
async deleteUser(req: IAuthRequest, res: Response): Promise<void> {
|
||||||
@ -235,14 +392,20 @@ export default class UserAdminController extends Controller {
|
|||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
async validatePassword(req: IAuthRequest, res: Response): Promise<void> {
|
async validatePassword(
|
||||||
|
req: IAuthRequest<unknown, unknown, PasswordSchema>,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
|
|
||||||
this.userService.validatePassword(password);
|
this.userService.validatePassword(password);
|
||||||
res.status(200).send();
|
res.status(200).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
async changePassword(req: IAuthRequest, res: Response): Promise<void> {
|
async changePassword(
|
||||||
|
req: IAuthRequest<{ id: string }, unknown, PasswordSchema>,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
const { password } = req.body;
|
const { password } = req.body;
|
||||||
|
|
||||||
|
@ -8,11 +8,13 @@ import UserService from '../../services/user-service';
|
|||||||
import UserFeedbackService from '../../services/user-feedback-service';
|
import UserFeedbackService from '../../services/user-feedback-service';
|
||||||
import UserSplashService from '../../services/user-splash-service';
|
import UserSplashService from '../../services/user-splash-service';
|
||||||
import { ADMIN, NONE } from '../../types/permissions';
|
import { ADMIN, NONE } from '../../types/permissions';
|
||||||
|
import { OpenApiService } from '../../services/openapi-service';
|
||||||
interface IChangeUserRequest {
|
import { createRequestSchema, createResponseSchema } from '../../openapi';
|
||||||
password: string;
|
import { emptyResponse } from '../../openapi/spec/empty-response';
|
||||||
confirmPassword: string;
|
import { meSchema, MeSchema } from '../../openapi/spec/me-schema';
|
||||||
}
|
import { serializeDates } from '../../types/serialize-dates';
|
||||||
|
import { IUserPermission } from '../../types/stores/access-store';
|
||||||
|
import { PasswordSchema } from '../../openapi/spec/password-schema';
|
||||||
|
|
||||||
class UserController extends Controller {
|
class UserController extends Controller {
|
||||||
private accessService: AccessService;
|
private accessService: AccessService;
|
||||||
@ -23,6 +25,8 @@ class UserController extends Controller {
|
|||||||
|
|
||||||
private userSplashService: UserSplashService;
|
private userSplashService: UserSplashService;
|
||||||
|
|
||||||
|
private openApiService: OpenApiService;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
config: IUnleashConfig,
|
config: IUnleashConfig,
|
||||||
{
|
{
|
||||||
@ -30,12 +34,14 @@ class UserController extends Controller {
|
|||||||
userService,
|
userService,
|
||||||
userFeedbackService,
|
userFeedbackService,
|
||||||
userSplashService,
|
userSplashService,
|
||||||
|
openApiService,
|
||||||
}: Pick<
|
}: Pick<
|
||||||
IUnleashServices,
|
IUnleashServices,
|
||||||
| 'accessService'
|
| 'accessService'
|
||||||
| 'userService'
|
| 'userService'
|
||||||
| 'userFeedbackService'
|
| 'userFeedbackService'
|
||||||
| 'userSplashService'
|
| 'userSplashService'
|
||||||
|
| 'openApiService'
|
||||||
>,
|
>,
|
||||||
) {
|
) {
|
||||||
super(config);
|
super(config);
|
||||||
@ -43,15 +49,45 @@ class UserController extends Controller {
|
|||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.userFeedbackService = userFeedbackService;
|
this.userFeedbackService = userFeedbackService;
|
||||||
this.userSplashService = userSplashService;
|
this.userSplashService = userSplashService;
|
||||||
|
this.openApiService = openApiService;
|
||||||
|
|
||||||
this.get('/', this.getUser);
|
this.route({
|
||||||
this.post('/change-password', this.updateUserPass, NONE);
|
method: 'get',
|
||||||
|
path: '',
|
||||||
|
handler: this.getMe,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'getMe',
|
||||||
|
responses: { 200: createResponseSchema('meSchema') },
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
|
||||||
|
this.route({
|
||||||
|
method: 'post',
|
||||||
|
path: '/change-password',
|
||||||
|
handler: this.changeMyPassword,
|
||||||
|
permission: NONE,
|
||||||
|
middleware: [
|
||||||
|
openApiService.validPath({
|
||||||
|
tags: ['admin'],
|
||||||
|
operationId: 'changeMyPassword',
|
||||||
|
requestBody: createRequestSchema('passwordSchema'),
|
||||||
|
responses: {
|
||||||
|
200: emptyResponse,
|
||||||
|
400: { description: 'passwordMismatch' },
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUser(req: IAuthRequest, res: Response): Promise<void> {
|
async getMe(req: IAuthRequest, res: Response<MeSchema>): Promise<void> {
|
||||||
res.setHeader('cache-control', 'no-store');
|
res.setHeader('cache-control', 'no-store');
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
let permissions;
|
let permissions: IUserPermission[];
|
||||||
if (this.config.authentication.type === IAuthType.NONE) {
|
if (this.config.authentication.type === IAuthType.NONE) {
|
||||||
permissions = [{ permission: ADMIN }];
|
permissions = [{ permission: ADMIN }];
|
||||||
} else {
|
} else {
|
||||||
@ -60,16 +96,25 @@ class UserController extends Controller {
|
|||||||
const feedback = await this.userFeedbackService.getAllUserFeedback(
|
const feedback = await this.userFeedbackService.getAllUserFeedback(
|
||||||
user,
|
user,
|
||||||
);
|
);
|
||||||
const splash = await this.userSplashService.getAllUserSplashs(user);
|
const splash = await this.userSplashService.getAllUserSplashes(user);
|
||||||
|
|
||||||
return res
|
const responseData: MeSchema = {
|
||||||
.status(200)
|
user: serializeDates(user),
|
||||||
.json({ user, permissions, feedback, splash })
|
permissions,
|
||||||
.end();
|
feedback: serializeDates(feedback),
|
||||||
|
splash,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.openApiService.respondWithValidation(
|
||||||
|
200,
|
||||||
|
res,
|
||||||
|
meSchema.$id,
|
||||||
|
responseData,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateUserPass(
|
async changeMyPassword(
|
||||||
req: IAuthRequest<any, any, IChangeUserRequest, any>,
|
req: IAuthRequest<unknown, unknown, PasswordSchema>,
|
||||||
res: Response,
|
res: Response,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
tokenUserSchema,
|
tokenUserSchema,
|
||||||
TokenUserSchema,
|
TokenUserSchema,
|
||||||
} from '../../openapi/spec/token-user-schema';
|
} from '../../openapi/spec/token-user-schema';
|
||||||
|
import { EmailSchema } from '../../openapi/spec/email-schema';
|
||||||
|
|
||||||
interface IValidateQuery {
|
interface IValidateQuery {
|
||||||
token: string;
|
token: string;
|
||||||
@ -97,14 +98,17 @@ class ResetPasswordController extends Controller {
|
|||||||
openApiService.validPath({
|
openApiService.validPath({
|
||||||
tags: ['other'],
|
tags: ['other'],
|
||||||
operationId: 'sendResetPasswordEmail',
|
operationId: 'sendResetPasswordEmail',
|
||||||
requestBody: createRequestSchema('resetPasswordSchema'),
|
requestBody: createRequestSchema('emailSchema'),
|
||||||
responses: { 200: emptyResponse },
|
responses: { 200: emptyResponse },
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async sendResetPasswordEmail(req: Request, res: Response): Promise<void> {
|
async sendResetPasswordEmail(
|
||||||
|
req: Request<unknown, unknown, EmailSchema>,
|
||||||
|
res: Response,
|
||||||
|
): Promise<void> {
|
||||||
const { email } = req.body;
|
const { email } = req.body;
|
||||||
|
|
||||||
await this.userService.createResetPasswordEmail(email);
|
await this.userService.createResetPasswordEmail(email);
|
||||||
|
@ -27,6 +27,7 @@ import { roleSchema } from '../schema/role-schema';
|
|||||||
import { CUSTOM_ROLE_TYPE, ALL_PROJECTS, ALL_ENVS } from '../util/constants';
|
import { CUSTOM_ROLE_TYPE, ALL_PROJECTS, ALL_ENVS } from '../util/constants';
|
||||||
import { DEFAULT_PROJECT } from '../types/project';
|
import { DEFAULT_PROJECT } from '../types/project';
|
||||||
import InvalidOperationError from '../error/invalid-operation-error';
|
import InvalidOperationError from '../error/invalid-operation-error';
|
||||||
|
import BadDataError from '../error/bad-data-error';
|
||||||
|
|
||||||
const { ADMIN } = permissions;
|
const { ADMIN } = permissions;
|
||||||
|
|
||||||
@ -200,7 +201,7 @@ export class AccessService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`Could not find rootRole=${role}`);
|
throw new BadDataError(`Could not find rootRole=${role}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import assert from 'assert';
|
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
import owasp from 'owasp-password-strength-test';
|
import owasp from 'owasp-password-strength-test';
|
||||||
import Joi from 'joi';
|
import Joi from 'joi';
|
||||||
@ -19,13 +18,15 @@ import { IUnleashStores } from '../types/stores';
|
|||||||
import PasswordUndefinedError from '../error/password-undefined';
|
import PasswordUndefinedError from '../error/password-undefined';
|
||||||
import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events';
|
import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events';
|
||||||
import { IEventStore } from '../types/stores/event-store';
|
import { IEventStore } from '../types/stores/event-store';
|
||||||
import { IUserSearch, IUserStore } from '../types/stores/user-store';
|
import { IUserStore } from '../types/stores/user-store';
|
||||||
import { RoleName } from '../types/model';
|
import { RoleName } from '../types/model';
|
||||||
import SettingService from './setting-service';
|
import SettingService from './setting-service';
|
||||||
import { SimpleAuthSettings } from '../server-impl';
|
import { SimpleAuthSettings } from '../server-impl';
|
||||||
import { simpleAuthKey } from '../types/settings/simple-auth-settings';
|
import { simpleAuthKey } from '../types/settings/simple-auth-settings';
|
||||||
import DisabledError from '../error/disabled-error';
|
import DisabledError from '../error/disabled-error';
|
||||||
import PasswordMismatch from '../error/password-mismatch';
|
import PasswordMismatch from '../error/password-mismatch';
|
||||||
|
import BadDataError from '../error/bad-data-error';
|
||||||
|
import { isDefined } from '../util/isDefined';
|
||||||
|
|
||||||
const systemUser = new User({ id: -1, username: 'system' });
|
const systemUser = new User({ id: -1, username: 'system' });
|
||||||
|
|
||||||
@ -54,11 +55,14 @@ export interface ILoginUserRequest {
|
|||||||
interface IUserWithRole extends IUser {
|
interface IUserWithRole extends IUser {
|
||||||
rootRole: number;
|
rootRole: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IRoleDescription {
|
interface IRoleDescription {
|
||||||
|
id: number;
|
||||||
description: string;
|
description: string;
|
||||||
name: string;
|
name: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITokenUser extends IUpdateUser {
|
interface ITokenUser extends IUpdateUser {
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
token: string;
|
token: string;
|
||||||
@ -171,7 +175,7 @@ class UserService {
|
|||||||
return { ...user, rootRole: roleId };
|
return { ...user, rootRole: roleId };
|
||||||
}
|
}
|
||||||
|
|
||||||
async search(query: IUserSearch): Promise<IUser[]> {
|
async search(query: string): Promise<IUser[]> {
|
||||||
return this.store.search(query);
|
return this.store.search(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +187,9 @@ class UserService {
|
|||||||
{ username, email, name, password, rootRole }: ICreateUser,
|
{ username, email, name, password, rootRole }: ICreateUser,
|
||||||
updatedBy?: User,
|
updatedBy?: User,
|
||||||
): Promise<IUser> {
|
): Promise<IUser> {
|
||||||
assert.ok(username || email, 'You must specify username or email');
|
if (!username && !email) {
|
||||||
|
throw new BadDataError('You must specify username or email');
|
||||||
|
}
|
||||||
|
|
||||||
if (email) {
|
if (email) {
|
||||||
Joi.assert(email, Joi.string().email(), 'Email');
|
Joi.assert(email, Joi.string().email(), 'Email');
|
||||||
@ -236,15 +242,25 @@ class UserService {
|
|||||||
{ id, name, email, rootRole }: IUpdateUser,
|
{ id, name, email, rootRole }: IUpdateUser,
|
||||||
updatedBy?: User,
|
updatedBy?: User,
|
||||||
): Promise<IUser> {
|
): Promise<IUser> {
|
||||||
Joi.assert(email, Joi.string().email(), 'Email');
|
|
||||||
|
|
||||||
const preUser = await this.store.get(id);
|
const preUser = await this.store.get(id);
|
||||||
|
|
||||||
|
if (email) {
|
||||||
|
Joi.assert(email, Joi.string().email(), 'Email');
|
||||||
|
}
|
||||||
|
|
||||||
if (rootRole) {
|
if (rootRole) {
|
||||||
await this.accessService.setUserRootRole(id, rootRole);
|
await this.accessService.setUserRootRole(id, rootRole);
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = await this.store.update(id, { name, email });
|
const payload: Partial<IUser> = {
|
||||||
|
name: name || preUser.name,
|
||||||
|
email: email || preUser.email,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Empty updates will throw, so make sure we have something to update.
|
||||||
|
const user = Object.values(payload).some(isDefined)
|
||||||
|
? await this.store.update(id, payload)
|
||||||
|
: preUser;
|
||||||
|
|
||||||
await this.eventStore.store({
|
await this.eventStore.store({
|
||||||
type: USER_UPDATED,
|
type: USER_UPDATED,
|
||||||
@ -359,6 +375,7 @@ class UserService {
|
|||||||
name: user.name,
|
name: user.name,
|
||||||
id: user.id,
|
id: user.id,
|
||||||
role: {
|
role: {
|
||||||
|
id: user.rootRole,
|
||||||
description: role.role.description,
|
description: role.role.description,
|
||||||
type: role.role.type,
|
type: role.role.type,
|
||||||
name: role.role.name,
|
name: role.role.name,
|
||||||
|
@ -20,13 +20,13 @@ export default class UserSplashService {
|
|||||||
this.logger = getLogger('services/user-splash-service.js');
|
this.logger = getLogger('services/user-splash-service.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllUserSplashs(user: User): Promise<Object> {
|
async getAllUserSplashes(user: User): Promise<Record<string, boolean>> {
|
||||||
if (user.isAPI) {
|
if (user.isAPI) {
|
||||||
return [];
|
return {};
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const splashs = (
|
return (
|
||||||
await this.userSplashStore.getAllUserSplashs(user.id)
|
await this.userSplashStore.getAllUserSplashes(user.id)
|
||||||
).reduce(
|
).reduce(
|
||||||
(splashObject, splash) => ({
|
(splashObject, splash) => ({
|
||||||
...splashObject,
|
...splashObject,
|
||||||
@ -34,10 +34,8 @@ export default class UserSplashService {
|
|||||||
}),
|
}),
|
||||||
{},
|
{},
|
||||||
);
|
);
|
||||||
return splashs;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.logger.error(err);
|
this.logger.error(err);
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ export interface IUserSplashKey {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserSplashStore extends Store<IUserSplash, IUserSplashKey> {
|
export interface IUserSplashStore extends Store<IUserSplash, IUserSplashKey> {
|
||||||
getAllUserSplashs(userId: number): Promise<IUserSplash[]>;
|
getAllUserSplashes(userId: number): Promise<IUserSplash[]>;
|
||||||
getSplash(userId: number, splashId: string): Promise<IUserSplash>;
|
getSplash(userId: number, splashId: string): Promise<IUserSplash>;
|
||||||
updateSplash(splash: IUserSplash): Promise<IUserSplash>;
|
updateSplash(splash: IUserSplash): Promise<IUserSplash>;
|
||||||
}
|
}
|
||||||
|
@ -14,12 +14,6 @@ export interface IUserLookup {
|
|||||||
email?: string;
|
email?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IUserSearch {
|
|
||||||
name?: string;
|
|
||||||
username?: string;
|
|
||||||
email: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface IUserUpdateFields {
|
export interface IUserUpdateFields {
|
||||||
name?: string;
|
name?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
@ -30,7 +24,7 @@ export interface IUserStore extends Store<IUser, number> {
|
|||||||
insert(user: ICreateUser): Promise<IUser>;
|
insert(user: ICreateUser): Promise<IUser>;
|
||||||
upsert(user: ICreateUser): Promise<IUser>;
|
upsert(user: ICreateUser): Promise<IUser>;
|
||||||
hasUser(idQuery: IUserLookup): Promise<number | undefined>;
|
hasUser(idQuery: IUserLookup): Promise<number | undefined>;
|
||||||
search(query: IUserSearch): Promise<IUser[]>;
|
search(query: string): Promise<IUser[]>;
|
||||||
getAllWithId(userIdList: number[]): Promise<IUser[]>;
|
getAllWithId(userIdList: number[]): Promise<IUser[]>;
|
||||||
getByQuery(idQuery: IUserLookup): Promise<IUser>;
|
getByQuery(idQuery: IUserLookup): Promise<IUser>;
|
||||||
getPasswordHash(userId: number): Promise<string>;
|
getPasswordHash(userId: number): Promise<string>;
|
||||||
|
8
src/lib/util/isDefined.test.ts
Normal file
8
src/lib/util/isDefined.test.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { isDefined } from './isDefined';
|
||||||
|
|
||||||
|
test('isDefined', () => {
|
||||||
|
expect(isDefined(null)).toEqual(false);
|
||||||
|
expect(isDefined(undefined)).toEqual(false);
|
||||||
|
expect(isDefined(0)).toEqual(true);
|
||||||
|
expect(isDefined(false)).toEqual(true);
|
||||||
|
});
|
3
src/lib/util/isDefined.ts
Normal file
3
src/lib/util/isDefined.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export const isDefined = <T>(value: T | null | undefined): value is T => {
|
||||||
|
return value !== null && typeof value !== 'undefined';
|
||||||
|
};
|
@ -11,6 +11,8 @@ import { IEventStore } from '../../../../lib/types/stores/event-store';
|
|||||||
import { IUserStore } from '../../../../lib/types/stores/user-store';
|
import { IUserStore } from '../../../../lib/types/stores/user-store';
|
||||||
import { RoleName } from '../../../../lib/types/model';
|
import { RoleName } from '../../../../lib/types/model';
|
||||||
import { IRoleStore } from 'lib/types/stores/role-store';
|
import { IRoleStore } from 'lib/types/stores/role-store';
|
||||||
|
import { randomId } from '../../../../lib/util/random-id';
|
||||||
|
import { omitKeys } from '../../../../lib/util/omit-keys';
|
||||||
|
|
||||||
let stores;
|
let stores;
|
||||||
let db;
|
let db;
|
||||||
@ -133,6 +135,19 @@ test('requires known root role', async () => {
|
|||||||
.expect(400);
|
.expect(400);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should require username or email on create', async () => {
|
||||||
|
await app.request
|
||||||
|
.post('/api/admin/user-admin')
|
||||||
|
.send({ rootRole: adminRole.id })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(400)
|
||||||
|
.expect((res) => {
|
||||||
|
expect(res.body.details[0].message).toEqual(
|
||||||
|
'You must specify username or email',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('update user name', async () => {
|
test('update user name', async () => {
|
||||||
const { body } = await app.request
|
const { body } = await app.request
|
||||||
.post('/api/admin/user-admin')
|
.post('/api/admin/user-admin')
|
||||||
@ -157,6 +172,24 @@ test('update user name', async () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should not require any fields on update', async () => {
|
||||||
|
const { body: created } = await app.request
|
||||||
|
.post('/api/admin/user-admin')
|
||||||
|
.send({ email: `${randomId()}@example.com`, rootRole: editorRole.id })
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(201);
|
||||||
|
|
||||||
|
const { body: updated } = await app.request
|
||||||
|
.put(`/api/admin/user-admin/${created.id}`)
|
||||||
|
.send({})
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.expect(200);
|
||||||
|
|
||||||
|
expect(updated).toEqual(
|
||||||
|
omitKeys(created, 'emailSent', 'inviteLink', 'rootRole'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('get a single user', async () => {
|
test('get a single user', async () => {
|
||||||
const { body } = await app.request
|
const { body } = await app.request
|
||||||
.post('/api/admin/user-admin')
|
.post('/api/admin/user-admin')
|
||||||
|
@ -488,6 +488,45 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"createUserSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"email": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"password": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"rootRole": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"sendEmail": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"username": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"rootRole",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"emailSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"email": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"email",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"environmentSchema": Object {
|
"environmentSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -953,6 +992,18 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"idSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"id": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"id",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"legalValueSchema": Object {
|
"legalValueSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -968,6 +1019,39 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"meSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"feedback": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/feedbackSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"permissions": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/permissionSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"splash": Object {
|
||||||
|
"additionalProperties": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"user": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"user",
|
||||||
|
"permissions",
|
||||||
|
"feedback",
|
||||||
|
"splash",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"nameSchema": Object {
|
"nameSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -1005,6 +1089,21 @@ Object {
|
|||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"passwordSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"confirmPassword": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"password": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"password",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"patchSchema": Object {
|
"patchSchema": Object {
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"from": Object {
|
"from": Object {
|
||||||
@ -1037,6 +1136,24 @@ Object {
|
|||||||
},
|
},
|
||||||
"type": "array",
|
"type": "array",
|
||||||
},
|
},
|
||||||
|
"permissionSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"environment": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"permission": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"project": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"permission",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"projectEnvironmentSchema": Object {
|
"projectEnvironmentSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -1108,21 +1225,24 @@ Object {
|
|||||||
"resetPasswordSchema": Object {
|
"resetPasswordSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"email": Object {
|
"resetPasswordUrl": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"email",
|
"resetPasswordUrl",
|
||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
"roleDescriptionSchema": Object {
|
"roleSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"description": Object {
|
"description": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
|
"id": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
"name": Object {
|
"name": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
@ -1131,9 +1251,9 @@ Object {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
"required": Array [
|
"required": Array [
|
||||||
"description",
|
"id",
|
||||||
"name",
|
|
||||||
"type",
|
"type",
|
||||||
|
"name",
|
||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
@ -1385,7 +1505,7 @@ Object {
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
},
|
},
|
||||||
"role": Object {
|
"role": Object {
|
||||||
"$ref": "#/components/schemas/roleDescriptionSchema",
|
"$ref": "#/components/schemas/roleSchema",
|
||||||
},
|
},
|
||||||
"token": Object {
|
"token": Object {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@ -1550,6 +1670,21 @@ Object {
|
|||||||
},
|
},
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"updateUserSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"email": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"rootRole": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
"upsertContextFieldSchema": Object {
|
"upsertContextFieldSchema": Object {
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
"description": Object {
|
"description": Object {
|
||||||
@ -1576,6 +1711,81 @@ Object {
|
|||||||
],
|
],
|
||||||
"type": "object",
|
"type": "object",
|
||||||
},
|
},
|
||||||
|
"userSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"createdAt": Object {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"email": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"emailSent": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"id": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"imageUrl": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"inviteLink": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"isAPI": Object {
|
||||||
|
"type": "boolean",
|
||||||
|
},
|
||||||
|
"loginAttempts": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"name": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"rootRole": Object {
|
||||||
|
"type": "number",
|
||||||
|
},
|
||||||
|
"seenAt": Object {
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
"username": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"id",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"usersSchema": Object {
|
||||||
|
"additionalProperties": false,
|
||||||
|
"properties": Object {
|
||||||
|
"rootRoles": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/roleSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
"users": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"required": Array [
|
||||||
|
"users",
|
||||||
|
],
|
||||||
|
"type": "object",
|
||||||
|
},
|
||||||
|
"usersSearchSchema": Object {
|
||||||
|
"items": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
"type": "array",
|
||||||
|
},
|
||||||
"validatePasswordSchema": Object {
|
"validatePasswordSchema": Object {
|
||||||
"additionalProperties": false,
|
"additionalProperties": false,
|
||||||
"properties": Object {
|
"properties": Object {
|
||||||
@ -4098,6 +4308,301 @@ Object {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"/api/admin/user": Object {
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "getMe",
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/meSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "meSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin": Object {
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "getUsers",
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/usersSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "usersSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "createUser",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/createUserSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "createUserSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin/reset-password": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "resetPassword",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/idSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "idSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/resetPasswordSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "resetPasswordSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin/search": Object {
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "searchUsers",
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/usersSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "usersSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin/validate-password": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "validatePassword",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/passwordSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "passwordSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"description": "emptyResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin/{id}": Object {
|
||||||
|
"delete": Object {
|
||||||
|
"operationId": "deleteUser",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"description": "emptyResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"get": Object {
|
||||||
|
"operationId": "getUser",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
"put": Object {
|
||||||
|
"operationId": "updateUser",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/updateUserSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "updateUserSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "userSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user-admin/{id}/change-password": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "changePassword",
|
||||||
|
"parameters": Array [
|
||||||
|
Object {
|
||||||
|
"in": "path",
|
||||||
|
"name": "id",
|
||||||
|
"required": true,
|
||||||
|
"schema": Object {
|
||||||
|
"type": "string",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/passwordSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "passwordSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"description": "emptyResponse",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"/api/admin/user/change-password": Object {
|
||||||
|
"post": Object {
|
||||||
|
"operationId": "changeMyPassword",
|
||||||
|
"requestBody": Object {
|
||||||
|
"content": Object {
|
||||||
|
"application/json": Object {
|
||||||
|
"schema": Object {
|
||||||
|
"$ref": "#/components/schemas/passwordSchema",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": "passwordSchema",
|
||||||
|
"required": true,
|
||||||
|
},
|
||||||
|
"responses": Object {
|
||||||
|
"200": Object {
|
||||||
|
"description": "emptyResponse",
|
||||||
|
},
|
||||||
|
"400": Object {
|
||||||
|
"description": "passwordMismatch",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"tags": Array [
|
||||||
|
"admin",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
"/auth/reset/password": Object {
|
"/auth/reset/password": Object {
|
||||||
"post": Object {
|
"post": Object {
|
||||||
"operationId": "changePassword",
|
"operationId": "changePassword",
|
||||||
@ -4129,11 +4634,11 @@ Object {
|
|||||||
"content": Object {
|
"content": Object {
|
||||||
"application/json": Object {
|
"application/json": Object {
|
||||||
"schema": Object {
|
"schema": Object {
|
||||||
"$ref": "#/components/schemas/resetPasswordSchema",
|
"$ref": "#/components/schemas/emailSchema",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"description": "resetPasswordSchema",
|
"description": "emailSchema",
|
||||||
"required": true,
|
"required": true,
|
||||||
},
|
},
|
||||||
"responses": Object {
|
"responses": Object {
|
||||||
|
@ -188,16 +188,13 @@ test('updating a user without an email should not strip the email', async () =>
|
|||||||
rootRole: adminRole.id,
|
rootRole: adminRole.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
await userService.updateUser({
|
await userService.updateUser({
|
||||||
id: user.id,
|
id: user.id,
|
||||||
email: null,
|
email: null,
|
||||||
name: 'some',
|
name: 'some',
|
||||||
});
|
});
|
||||||
} catch (e) {}
|
|
||||||
|
|
||||||
const updatedUser = await userService.getUser(user.id);
|
const updatedUser = await userService.getUser(user.id);
|
||||||
|
|
||||||
expect(updatedUser.email).toBe(email);
|
expect(updatedUser.email).toBe(email);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -31,7 +31,9 @@ test('should create userSplash', async () => {
|
|||||||
userId: currentUser.id,
|
userId: currentUser.id,
|
||||||
seen: false,
|
seen: false,
|
||||||
});
|
});
|
||||||
const userSplashs = await userSplashStore.getAllUserSplashs(currentUser.id);
|
const userSplashs = await userSplashStore.getAllUserSplashes(
|
||||||
|
currentUser.id,
|
||||||
|
);
|
||||||
expect(userSplashs).toHaveLength(1);
|
expect(userSplashs).toHaveLength(1);
|
||||||
expect(userSplashs[0].splashId).toBe('some-id');
|
expect(userSplashs[0].splashId).toBe('some-id');
|
||||||
});
|
});
|
||||||
|
2
src/test/fixtures/fake-user-splash-store.ts
vendored
2
src/test/fixtures/fake-user-splash-store.ts
vendored
@ -6,7 +6,7 @@ import {
|
|||||||
|
|
||||||
export default class FakeUserSplashStore implements IUserSplashStore {
|
export default class FakeUserSplashStore implements IUserSplashStore {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
getAllUserSplashs(userId: number): Promise<IUserSplash[]> {
|
getAllUserSplashes(userId: number): Promise<IUserSplash[]> {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -202,7 +202,8 @@ Updates use with new fields
|
|||||||
**Notes**
|
**Notes**
|
||||||
|
|
||||||
- `userId` is required as a url path parameter.
|
- `userId` is required as a url path parameter.
|
||||||
- You must provide _at least_ either `name` or `email`. All other fields are entirely optional. Only provided fields are updated.
|
- All fields are optional. Only provided fields are updated.
|
||||||
|
- Note that earlier versions of Unleash required either `name` or `email` to be set.
|
||||||
|
|
||||||
### Delete a user {#delete-a-user}
|
### Delete a user {#delete-a-user}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user