mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
refactor!: remove user roleNames (#2990)
This commit is contained in:
parent
47b84f2d78
commit
3e34c51cca
21 changed files with 85 additions and 212 deletions
|
@ -1,13 +1,12 @@
|
|||
import type { UserWithRoleNames } from '@logto/schemas';
|
||||
import type { User } from '@logto/schemas';
|
||||
import { userInfoSelectFields, UsersPasswordEncryptionMethod } from '@logto/schemas';
|
||||
import { pick } from '@silverhand/essentials';
|
||||
|
||||
export const mockUser: UserWithRoleNames = {
|
||||
export const mockUser: User = {
|
||||
id: 'foo',
|
||||
username: 'foo',
|
||||
primaryEmail: 'foo@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: 'password',
|
||||
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Argon2i,
|
||||
name: null,
|
||||
|
@ -25,12 +24,11 @@ export const mockUser: UserWithRoleNames = {
|
|||
export const mockUserResponse = pick(mockUser, ...userInfoSelectFields);
|
||||
|
||||
export const mockPasswordEncrypted = 'a1b2c3';
|
||||
export const mockUserWithPassword: UserWithRoleNames = {
|
||||
export const mockUserWithPassword: User = {
|
||||
id: 'id',
|
||||
username: 'username',
|
||||
primaryEmail: 'foo@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: mockPasswordEncrypted,
|
||||
passwordEncryptionMethod: UsersPasswordEncryptionMethod.Argon2i,
|
||||
name: null,
|
||||
|
@ -45,13 +43,12 @@ export const mockUserWithPassword: UserWithRoleNames = {
|
|||
isSuspended: false,
|
||||
};
|
||||
|
||||
export const mockUserList: UserWithRoleNames[] = [
|
||||
export const mockUserList: User[] = [
|
||||
{
|
||||
id: '1',
|
||||
username: 'foo1',
|
||||
primaryEmail: 'foo1@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
name: null,
|
||||
|
@ -68,7 +65,6 @@ export const mockUserList: UserWithRoleNames[] = [
|
|||
username: 'foo2',
|
||||
primaryEmail: 'foo2@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
name: null,
|
||||
|
@ -85,7 +81,6 @@ export const mockUserList: UserWithRoleNames[] = [
|
|||
username: 'foo3',
|
||||
primaryEmail: 'foo3@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
name: null,
|
||||
|
@ -102,7 +97,6 @@ export const mockUserList: UserWithRoleNames[] = [
|
|||
username: 'bar1',
|
||||
primaryEmail: 'bar1@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
name: null,
|
||||
|
@ -119,7 +113,6 @@ export const mockUserList: UserWithRoleNames[] = [
|
|||
username: 'bar2',
|
||||
primaryEmail: 'bar2@logto.io',
|
||||
primaryPhone: '111111',
|
||||
roleNames: ['admin'],
|
||||
passwordEncrypted: null,
|
||||
passwordEncryptionMethod: null,
|
||||
name: null,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { buildIdGenerator } from '@logto/core-kit';
|
||||
import type { User, CreateUser, Scope, UserWithRoleNames } from '@logto/schemas';
|
||||
import { Users, UsersPasswordEncryptionMethod } from '@logto/schemas';
|
||||
import type { User, CreateUser, Scope } from '@logto/schemas';
|
||||
import { Users, UsersPasswordEncryptionMethod, defaultRole } from '@logto/schemas';
|
||||
import type { OmitAutoSetFields } from '@logto/shared';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import { deduplicate } from '@silverhand/essentials';
|
||||
|
@ -65,20 +65,6 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
scopes: { findScopesByIdsAndResourceId },
|
||||
} = queries;
|
||||
|
||||
// TODO: @sijie remove this if no need for `UserWithRoleNames` anymore
|
||||
const findUserByIdWithRoles = async (id: string): Promise<UserWithRoleNames> => {
|
||||
const user = await findUserById(id);
|
||||
const userRoles = await findUsersRolesByUserId(user.id);
|
||||
|
||||
const roles =
|
||||
userRoles.length > 0 ? await findRolesByRoleIds(userRoles.map(({ roleId }) => roleId)) : [];
|
||||
|
||||
return {
|
||||
...user,
|
||||
roleNames: roles.map(({ name }) => name),
|
||||
};
|
||||
};
|
||||
|
||||
const generateUserId = async (retries = 500) =>
|
||||
pRetry(
|
||||
async () => {
|
||||
|
@ -97,47 +83,20 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
returning: true,
|
||||
});
|
||||
|
||||
// Temp solution since Hasura requires a role to proceed authn.
|
||||
// The source of default roles should be guarded and moved to database once we implement RBAC.
|
||||
const insertUser = async ({
|
||||
roleNames,
|
||||
...rest
|
||||
}: OmitAutoSetFields<CreateUser> & { roleNames?: string[] }) => {
|
||||
const computedRoleNames = deduplicate(
|
||||
(roleNames ?? []).concat(EnvSet.values.userDefaultRoleNames)
|
||||
);
|
||||
|
||||
if (computedRoleNames.length > 0) {
|
||||
const existingRoles = await findRolesByRoleNames(computedRoleNames);
|
||||
const missingRoleNames = computedRoleNames.filter(
|
||||
(roleName) => !existingRoles.some(({ name }) => roleName === name)
|
||||
);
|
||||
|
||||
if (missingRoleNames.length > 0) {
|
||||
await insertRoles(
|
||||
missingRoleNames.map((name) => ({
|
||||
id: roleId(),
|
||||
name,
|
||||
description: 'User default role.',
|
||||
}))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const user = await insertUserQuery(rest);
|
||||
|
||||
await Promise.all([
|
||||
computedRoleNames.map(async (roleName) => {
|
||||
const role = await findRoleByRoleName(roleName);
|
||||
|
||||
if (!role) {
|
||||
// Not expected to happen, just inserted above, so is 500
|
||||
throw new Error(`Can not find role: ${roleName}`);
|
||||
}
|
||||
|
||||
await insertUsersRoles([{ userId: user.id, roleId: role.id }]);
|
||||
}),
|
||||
const insertUser = async (data: OmitAutoSetFields<CreateUser>, isAdmin = false) => {
|
||||
const roleNames = deduplicate([
|
||||
...EnvSet.values.userDefaultRoleNames,
|
||||
...(isAdmin ? [defaultRole.name] : []),
|
||||
]);
|
||||
const roles = await findRolesByRoleNames(roleNames);
|
||||
|
||||
assertThat(roles.length === roleNames.length, 'role.default_role_missing');
|
||||
|
||||
const user = await insertUserQuery(data);
|
||||
|
||||
if (roles.length > 0) {
|
||||
await insertUsersRoles(roles.map(({ id }) => ({ userId: user.id, roleId: id })));
|
||||
}
|
||||
|
||||
return user;
|
||||
};
|
||||
|
@ -203,7 +162,6 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
};
|
||||
|
||||
return {
|
||||
findUserByIdWithRoles,
|
||||
generateUserId,
|
||||
insertUser,
|
||||
checkIdentifierCollision,
|
||||
|
|
|
@ -32,8 +32,9 @@ export default function initOidc(envSet: EnvSet, queries: Queries, libraries: Li
|
|||
} = envSet.oidc;
|
||||
const {
|
||||
resources: { findResourceByIndicator },
|
||||
users: { findUserById },
|
||||
} = queries;
|
||||
const { findUserByIdWithRoles, findUserScopesForResourceId } = libraries.users;
|
||||
const { findUserScopesForResourceId } = libraries.users;
|
||||
const { findApplicationScopesForResourceId } = libraries.applications;
|
||||
const logoutSource = readFileSync('static/html/logout.html', 'utf8');
|
||||
|
||||
|
@ -153,7 +154,7 @@ export default function initOidc(envSet: EnvSet, queries: Queries, libraries: Li
|
|||
claims: userClaims,
|
||||
// https://github.com/panva/node-oidc-provider/tree/main/docs#findaccount
|
||||
findAccount: async (_ctx, sub) => {
|
||||
const user = await findUserByIdWithRoles(sub);
|
||||
const user = await findUserById(sub);
|
||||
|
||||
return {
|
||||
accountId: sub,
|
||||
|
|
|
@ -11,14 +11,12 @@ describe('OIDC getUserClaims()', () => {
|
|||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
|
||||
expect(getUserClaims(use.idToken, 'openid profile email phone', {}, [])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'email',
|
||||
'email_verified',
|
||||
'phone_number',
|
||||
|
@ -29,14 +27,12 @@ describe('OIDC getUserClaims()', () => {
|
|||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
|
||||
expect(getUserClaims(use.idToken, 'openid profile email', {}, ['email_verified'])).toEqual([
|
||||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'email',
|
||||
]);
|
||||
});
|
||||
|
@ -46,7 +42,6 @@ describe('OIDC getUserClaims()', () => {
|
|||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
'custom_data',
|
||||
'identities',
|
||||
]);
|
||||
|
@ -58,7 +53,6 @@ describe('OIDC getUserClaims()', () => {
|
|||
'name',
|
||||
'picture',
|
||||
'username',
|
||||
'role_names',
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,14 +1,13 @@
|
|||
import type { UserClaim } from '@logto/core-kit';
|
||||
import { idTokenClaims, userinfoClaims, UserScope } from '@logto/core-kit';
|
||||
import type { UserWithRoleNames } from '@logto/schemas';
|
||||
import type { User } from '@logto/schemas';
|
||||
import type { Nullable } from '@silverhand/essentials';
|
||||
import type { ClaimsParameterMember } from 'oidc-provider';
|
||||
|
||||
export const claimToUserKey: Readonly<Record<UserClaim, keyof UserWithRoleNames>> = Object.freeze({
|
||||
export const claimToUserKey: Readonly<Record<UserClaim, keyof User>> = Object.freeze({
|
||||
name: 'name',
|
||||
picture: 'avatar',
|
||||
username: 'username',
|
||||
role_names: 'roleNames',
|
||||
email: 'primaryEmail',
|
||||
// LOG-4165: Change to proper key/function once profile fulfilling implemented
|
||||
email_verified: 'primaryEmail',
|
||||
|
|
|
@ -37,7 +37,6 @@ describe('user query', () => {
|
|||
const { table, fields } = convertToIdentifiers(Users);
|
||||
const databaseValue = {
|
||||
...mockUser,
|
||||
roleNames: JSON.stringify(mockUser.roleNames),
|
||||
identities: JSON.stringify(mockUser.identities),
|
||||
customData: JSON.stringify(mockUser.customData),
|
||||
};
|
||||
|
@ -276,7 +275,6 @@ describe('user query', () => {
|
|||
const { connector1, ...restIdentities } = mockUser.identities;
|
||||
const finalDbvalue = {
|
||||
...mockUser,
|
||||
roleNames: JSON.stringify(mockUser.roleNames),
|
||||
identities: JSON.stringify(restIdentities),
|
||||
customData: JSON.stringify(mockUser.customData),
|
||||
};
|
||||
|
|
|
@ -82,7 +82,6 @@ const mockHasUserWithPhone = jest.fn(async () => false);
|
|||
const { revokeInstanceByUserId } = mockedQueries.oidcModelInstances;
|
||||
const { hasUser, findUserById, updateUserById, deleteUserIdentity, deleteUserById } =
|
||||
mockedQueries.users;
|
||||
const { findRolesByRoleNames } = mockedQueries.roles;
|
||||
|
||||
const { encryptUserPassword } = await mockEsmWithActual('#src/libraries/user.js', () => ({
|
||||
encryptUserPassword: jest.fn(() => ({
|
||||
|
@ -92,7 +91,6 @@ const { encryptUserPassword } = await mockEsmWithActual('#src/libraries/user.js'
|
|||
}));
|
||||
|
||||
const usersLibraries = {
|
||||
findUserByIdWithRoles: jest.fn(async (id: string) => mockUser),
|
||||
generateUserId: jest.fn(async () => 'fooId'),
|
||||
insertUser: jest.fn(
|
||||
async (user: CreateUser): Promise<User> => ({
|
||||
|
@ -101,7 +99,6 @@ const usersLibraries = {
|
|||
})
|
||||
),
|
||||
} satisfies Partial<Libraries['users']>;
|
||||
const { findUserByIdWithRoles } = usersLibraries;
|
||||
|
||||
const adminUserRoutes = await pickDefault(import('./admin-user.js'));
|
||||
|
||||
|
@ -136,7 +133,7 @@ describe('adminUserRoutes', () => {
|
|||
it('GET /users/:userId', async () => {
|
||||
const response = await userRequest.get('/users/foo');
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual({ ...mockUserResponse, roleNames: ['admin'] });
|
||||
expect(response.body).toEqual(mockUserResponse);
|
||||
});
|
||||
|
||||
it('POST /users', async () => {
|
||||
|
@ -154,7 +151,6 @@ describe('adminUserRoutes', () => {
|
|||
id: 'fooId',
|
||||
username,
|
||||
name,
|
||||
roleNames: undefined, // API will call `insertUser()` with `roleNames` specified
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -281,7 +277,7 @@ describe('adminUserRoutes', () => {
|
|||
const name = 'Michael';
|
||||
const avatar = 'http://www.michael.png';
|
||||
|
||||
findUserByIdWithRoles.mockImplementationOnce(() => {
|
||||
findUserById.mockImplementationOnce(() => {
|
||||
throw new Error(' ');
|
||||
});
|
||||
|
||||
|
@ -316,28 +312,6 @@ describe('adminUserRoutes', () => {
|
|||
).resolves.toHaveProperty('status', 422);
|
||||
});
|
||||
|
||||
it('PATCH /users/:userId should throw if role names are invalid', async () => {
|
||||
findRolesByRoleNames.mockImplementationOnce(
|
||||
async (): Promise<Role[]> => [
|
||||
{ id: 'role_id1', name: 'worker', description: 'none' },
|
||||
{ id: 'role_id2', name: 'cleaner', description: 'none' },
|
||||
]
|
||||
);
|
||||
await expect(
|
||||
userRequest.patch('/users/foo').send({ roleNames: ['superadmin'] })
|
||||
).resolves.toHaveProperty('status', 400);
|
||||
expect(findUserByIdWithRoles).toHaveBeenCalledTimes(1);
|
||||
expect(updateUserById).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('PATCH /users/:userId should update if roleNames field is an empty array', async () => {
|
||||
const roleNames: string[] = [];
|
||||
|
||||
const response = await userRequest.patch('/users/foo').send({ roleNames });
|
||||
expect(response.status).toEqual(200);
|
||||
expect(response.body).toEqual(mockUserResponse);
|
||||
});
|
||||
|
||||
it('PATCH /users/:userId/password', async () => {
|
||||
const mockedUserId = 'foo';
|
||||
const password = '123456';
|
||||
|
|
|
@ -31,16 +31,10 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
hasUserWithEmail,
|
||||
hasUserWithPhone,
|
||||
},
|
||||
usersRoles: { deleteUsersRolesByUserIdAndRoleId, findUsersRolesByRoleId, insertUsersRoles },
|
||||
usersRoles: { findUsersRolesByRoleId },
|
||||
} = queries;
|
||||
const {
|
||||
users: {
|
||||
checkIdentifierCollision,
|
||||
generateUserId,
|
||||
insertUser,
|
||||
findUsersByRoleName,
|
||||
findUserByIdWithRoles,
|
||||
},
|
||||
users: { checkIdentifierCollision, generateUserId, insertUser, findUsersByRoleName },
|
||||
} = libraries;
|
||||
|
||||
router.get('/users', koaPagination(), async (ctx, next) => {
|
||||
|
@ -87,9 +81,9 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
params: { userId },
|
||||
} = ctx.guard;
|
||||
|
||||
const user = await findUserByIdWithRoles(userId);
|
||||
const user = await findUserById(userId);
|
||||
|
||||
ctx.body = pick(user, 'roleNames', ...userInfoSelectFields);
|
||||
ctx.body = pick(user, ...userInfoSelectFields);
|
||||
|
||||
return next();
|
||||
}
|
||||
|
@ -174,15 +168,17 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
|
||||
const id = await generateUserId();
|
||||
|
||||
const user = await insertUser({
|
||||
id,
|
||||
primaryEmail,
|
||||
primaryPhone,
|
||||
username,
|
||||
name,
|
||||
roleNames: conditional(isAdmin && [UserRole.Admin]),
|
||||
...conditional(password && (await encryptUserPassword(password))),
|
||||
});
|
||||
const user = await insertUser(
|
||||
{
|
||||
id,
|
||||
primaryEmail,
|
||||
primaryPhone,
|
||||
username,
|
||||
name,
|
||||
...conditional(password && (await encryptUserPassword(password))),
|
||||
},
|
||||
isAdmin
|
||||
);
|
||||
|
||||
ctx.body = pick(user, ...userInfoSelectFields);
|
||||
|
||||
|
@ -201,7 +197,6 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
name: string().or(literal('')).nullable(),
|
||||
avatar: string().url().or(literal('')).nullable(),
|
||||
customData: arbitraryObjectGuard,
|
||||
roleNames: string().array(),
|
||||
}).partial(),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
|
@ -210,56 +205,10 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
body,
|
||||
} = ctx.guard;
|
||||
|
||||
const user = await findUserByIdWithRoles(userId);
|
||||
await findUserById(userId);
|
||||
await checkIdentifierCollision(body, userId);
|
||||
|
||||
const { roleNames, ...userUpdates } = body;
|
||||
|
||||
// Temp solution to validate the existence of input roleNames
|
||||
if (roleNames) {
|
||||
const roles = await findRolesByRoleNames(roleNames);
|
||||
|
||||
// Insert new roles
|
||||
const newRoles = roleNames.filter((roleName) => !user.roleNames.includes(roleName));
|
||||
|
||||
if (newRoles.length > 0) {
|
||||
await insertUsersRoles(
|
||||
newRoles.map((roleName) => {
|
||||
const role = roles.find(({ name }) => name === roleName);
|
||||
|
||||
if (!role) {
|
||||
throw new RequestError({
|
||||
status: 400,
|
||||
code: 'user.invalid_role_names',
|
||||
data: {
|
||||
roleNames: roleName,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
userId: user.id,
|
||||
roleId: role.id,
|
||||
};
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Remove old roles
|
||||
const oldRoles = user.roleNames.filter((roleName) => !roleNames.includes(roleName));
|
||||
|
||||
await Promise.all(
|
||||
oldRoles.map(async (roleName) => {
|
||||
const role = roles.find(({ name }) => name === roleName);
|
||||
|
||||
if (role) {
|
||||
await deleteUsersRolesByUserIdAndRoleId(user.id, role.id);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const updatedUser = await updateUserById(userId, userUpdates, 'replace');
|
||||
const updatedUser = await updateUserById(userId, body, 'replace');
|
||||
ctx.body = pick(updatedUser, ...userInfoSelectFields);
|
||||
|
||||
return next();
|
||||
|
@ -363,8 +312,6 @@ export default function adminUserRoutes<T extends AuthedRouter>(
|
|||
ctx.body = pick(updatedUser, ...userInfoSelectFields);
|
||||
|
||||
return next();
|
||||
// TODO: @sijie break into smaller files
|
||||
// eslint-disable-next-line max-lines
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { InteractionEvent, adminConsoleApplicationId, UserRole } from '@logto/schemas';
|
||||
import { InteractionEvent, adminConsoleApplicationId } from '@logto/schemas';
|
||||
import { createMockUtils, pickDefault } from '@logto/shared/esm';
|
||||
import type Provider from 'oidc-provider';
|
||||
|
||||
|
@ -110,10 +110,13 @@ describe('submit action', () => {
|
|||
expect(encryptUserPassword).toBeCalledWith('password');
|
||||
expect(getLogtoConnectorById).toBeCalledWith('logto');
|
||||
|
||||
expect(insertUser).toBeCalledWith({
|
||||
id: 'uid',
|
||||
...upsertProfile,
|
||||
});
|
||||
expect(insertUser).toBeCalledWith(
|
||||
{
|
||||
id: 'uid',
|
||||
...upsertProfile,
|
||||
},
|
||||
false
|
||||
);
|
||||
expect(assignInteractionResults).toBeCalledWith(ctx, tenant.provider, {
|
||||
login: { accountId: 'uid' },
|
||||
});
|
||||
|
@ -145,11 +148,13 @@ describe('submit action', () => {
|
|||
expect(encryptUserPassword).toBeCalledWith('password');
|
||||
expect(getLogtoConnectorById).toBeCalledWith('logto');
|
||||
|
||||
expect(insertUser).toBeCalledWith({
|
||||
id: 'uid',
|
||||
roleNames: [UserRole.Admin],
|
||||
...upsertProfile,
|
||||
});
|
||||
expect(insertUser).toBeCalledWith(
|
||||
{
|
||||
id: 'uid',
|
||||
...upsertProfile,
|
||||
},
|
||||
true
|
||||
);
|
||||
expect(assignInteractionResults).toBeCalledWith(adminConsoleCtx, tenant.provider, {
|
||||
login: { accountId: 'uid' },
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { User, Profile } from '@logto/schemas';
|
||||
import { InteractionEvent, UserRole, adminConsoleApplicationId } from '@logto/schemas';
|
||||
import { InteractionEvent, adminConsoleApplicationId } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
|
||||
import type { ConnectorLibrary } from '#src/libraries/connector.js';
|
||||
|
@ -143,6 +143,7 @@ export default async function submitInteraction(
|
|||
log?: LogEntry
|
||||
) {
|
||||
const { hasActiveUsers, findUserById, updateUserById } = queries.users;
|
||||
const { insertUsersRoles } = queries.usersRoles;
|
||||
|
||||
const {
|
||||
users: { generateUserId, insertUser },
|
||||
|
@ -158,13 +159,14 @@ export default async function submitInteraction(
|
|||
|
||||
const createAdminUser =
|
||||
String(client_id) === adminConsoleApplicationId && !(await hasActiveUsers());
|
||||
const roleNames = createAdminUser ? [UserRole.Admin] : undefined;
|
||||
|
||||
await insertUser({
|
||||
id,
|
||||
...conditional(roleNames && { roleNames }),
|
||||
...upsertProfile,
|
||||
});
|
||||
await insertUser(
|
||||
{
|
||||
id,
|
||||
...upsertProfile,
|
||||
},
|
||||
createAdminUser
|
||||
);
|
||||
|
||||
await assignInteractionResults(ctx, provider, { login: { accountId: id } });
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ const errors = {
|
|||
phone_not_exist: 'Die Telefonnummer wurde noch nicht registriert.',
|
||||
identity_not_exist: 'Die Identität wurde noch nicht registriert.',
|
||||
identity_already_in_use: 'Die Identität wurde registriert.',
|
||||
invalid_role_names: 'Rollennamen ({{roleNames}}) sind ungültig',
|
||||
cannot_delete_self: 'Du kannst dich nicht selbst löschen.',
|
||||
sign_up_method_not_enabled: 'This sign-up method is not enabled.', // UNTRANSLATED
|
||||
sign_in_method_not_enabled: 'This sign-in method is not enabled.', // UNTRANSLATED
|
||||
|
@ -180,6 +179,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -45,7 +45,6 @@ const errors = {
|
|||
phone_not_exist: 'The phone number has not been registered yet.',
|
||||
identity_not_exist: 'The social account has not been registered yet.',
|
||||
identity_already_in_use: 'The social account has been associated with an existing account.',
|
||||
invalid_role_names: 'Role names ({{roleNames}}) are not valid.',
|
||||
cannot_delete_self: 'You cannot delete yourself.',
|
||||
sign_up_method_not_enabled: 'This sign-up method is not enabled.',
|
||||
sign_in_method_not_enabled: 'This sign-in method is not enabled.',
|
||||
|
@ -179,6 +178,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use',
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role',
|
||||
user_exists: 'The user id {{userId}} is already been added to this role',
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first',
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use',
|
||||
|
|
|
@ -46,7 +46,6 @@ const errors = {
|
|||
phone_not_exist: "Le numéro de téléphone n'a pas encore été enregistré.",
|
||||
identity_not_exist: "Le compte social n'a pas encore été enregistré.",
|
||||
identity_already_in_use: 'Le compte social a été enregistré.',
|
||||
invalid_role_names: 'Les noms de rôles ({{roleNames}}) ne sont pas valides',
|
||||
cannot_delete_self: 'You cannot delete yourself.', // UNTRANSLATED
|
||||
sign_up_method_not_enabled: 'This sign-up method is not enabled.', // UNTRANSLATED
|
||||
sign_in_method_not_enabled: 'This sign-in method is not enabled.', // UNTRANSLATED
|
||||
|
@ -186,6 +185,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -44,7 +44,6 @@ const errors = {
|
|||
phone_not_exist: '휴대전화번호가 아직 등록되지 않았어요.',
|
||||
identity_not_exist: '소셜 계정이 아직 등록되지 않았어요.',
|
||||
identity_already_in_use: '소셜 계정이 이미 등록되있어요.',
|
||||
invalid_role_names: '직책 명({{roleNames}})이 유효하지 않아요.',
|
||||
cannot_delete_self: '자기 자신을 삭제할 수 없어요.',
|
||||
sign_up_method_not_enabled: '이 회원가입 방법은 활성화 되어있지 않아요.',
|
||||
sign_in_method_not_enabled: '이 로그인 방법은 활성화 되어있지 않아요.',
|
||||
|
@ -173,6 +172,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -45,7 +45,6 @@ const errors = {
|
|||
phone_not_exist: 'O número de telefone ainda não foi registrado.',
|
||||
identity_not_exist: 'A conta social ainda não foi registrada.',
|
||||
identity_already_in_use: 'A conta social foi associada a uma conta existente.',
|
||||
invalid_role_names: 'Nomes de regra ({{roleNames}}) não são válidos.',
|
||||
cannot_delete_self: 'Você não pode excluir a si mesmo.',
|
||||
sign_up_method_not_enabled: 'Este método de inscrição não está ativado',
|
||||
sign_in_method_not_enabled: 'Este método de login não está habilitado.',
|
||||
|
@ -187,6 +186,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -44,7 +44,6 @@ const errors = {
|
|||
phone_not_exist: 'O numero do telefone ainda não foi registada.',
|
||||
identity_not_exist: 'A conta social ainda não foi registada.',
|
||||
identity_already_in_use: 'A conta social foi registada.',
|
||||
invalid_role_names: '({{roleNames}}) não são válidos',
|
||||
cannot_delete_self: 'Não se pode remover a si mesmo.',
|
||||
sign_up_method_not_enabled: 'This sign-up method is not enabled.', // UNTRANSLATED
|
||||
sign_in_method_not_enabled: 'This sign-in method is not enabled.', // UNTRANSLATED
|
||||
|
@ -181,6 +180,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -45,7 +45,6 @@ const errors = {
|
|||
phone_not_exist: 'Telefon numarası henüz kaydedilmedi',
|
||||
identity_not_exist: 'Sosyal platform hesabı henüz kaydedilmedi.',
|
||||
identity_already_in_use: 'Sosyal platform hesabı kaydedildi.',
|
||||
invalid_role_names: '({{roleNames}}) rol adları geçerli değil.',
|
||||
cannot_delete_self: 'You cannot delete yourself.', // UNTRANSLATED
|
||||
sign_up_method_not_enabled: 'This sign-up method is not enabled.', // UNTRANSLATED
|
||||
sign_in_method_not_enabled: 'This sign-in method is not enabled.', // UNTRANSLATED
|
||||
|
@ -181,6 +180,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -43,7 +43,6 @@ const errors = {
|
|||
phone_not_exist: '手机号码尚未注册。',
|
||||
identity_not_exist: '该社交帐号尚未注册。',
|
||||
identity_already_in_use: '该社交帐号已被注册。',
|
||||
invalid_role_names: '角色名称({{roleNames}})无效。',
|
||||
cannot_delete_self: '无法删除自己的账户。',
|
||||
sign_up_method_not_enabled: '注册方式尚未启用。',
|
||||
sign_in_method_not_enabled: '登录方式尚未启用。',
|
||||
|
@ -162,6 +161,8 @@ const errors = {
|
|||
name_in_use: 'This role name {{name}} is already in use', // UNTRANSLATED
|
||||
scope_exists: 'The scope id {{scopeId}} has already been added to this role', // UNTRANSLATED
|
||||
user_exists: 'The user id {{userId}} is already been added to this role', // UNTRANSLATED
|
||||
default_role_missing:
|
||||
'Some of the default roleNames does not exist in database, please ensure to create roles first', // UNTRANSLATED
|
||||
},
|
||||
scope: {
|
||||
name_exists: 'The scope name {{name}} is already in use', // UNTRANSLATED
|
||||
|
|
|
@ -80,8 +80,6 @@ export type CustomClientMetadata = z.infer<typeof customClientMetadataGuard>;
|
|||
*/
|
||||
export const roleNamesGuard = z.string().array();
|
||||
|
||||
export type RoleNames = z.infer<typeof roleNamesGuard>;
|
||||
|
||||
const identityGuard = z.object({
|
||||
userId: z.string(),
|
||||
details: z.object({}).optional(), // Connector's userinfo details, schemaless
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { CreateUser, User } from '../db-entries/index.js';
|
||||
import type { CreateUser } from '../db-entries/index.js';
|
||||
|
||||
export const userInfoSelectFields = Object.freeze([
|
||||
'id',
|
||||
|
@ -25,6 +25,3 @@ export type UserProfileResponse = UserInfo & { hasPasswordSet: boolean };
|
|||
export enum UserRole {
|
||||
Admin = 'admin',
|
||||
}
|
||||
|
||||
// FIXME @sijie remove this after RBAC is completed.
|
||||
export type UserWithRoleNames = User & { roleNames: string[] };
|
||||
|
|
|
@ -7,7 +7,6 @@ export type UserClaim =
|
|||
| 'name'
|
||||
| 'picture'
|
||||
| 'username'
|
||||
| 'role_names'
|
||||
| 'email'
|
||||
| 'email_verified'
|
||||
| 'phone_number'
|
||||
|
@ -55,7 +54,7 @@ export enum UserScope {
|
|||
* Mapped claims that ID Token includes.
|
||||
*/
|
||||
export const idTokenClaims: Readonly<Record<UserScope, UserClaim[]>> = Object.freeze({
|
||||
[UserScope.Profile]: ['name', 'picture', 'username', 'role_names'],
|
||||
[UserScope.Profile]: ['name', 'picture', 'username'],
|
||||
[UserScope.Email]: ['email', 'email_verified'],
|
||||
[UserScope.Phone]: ['phone_number', 'phone_number_verified'],
|
||||
[UserScope.CustomData]: [],
|
||||
|
|
Loading…
Reference in a new issue