0
Fork 0
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:
wangsijie 2023-01-20 16:51:29 +08:00 committed by GitHub
parent 47b84f2d78
commit 3e34c51cca
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
21 changed files with 85 additions and 212 deletions

View file

@ -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,

View file

@ -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,

View file

@ -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,

View file

@ -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',
]);
});
});

View file

@ -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',

View file

@ -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),
};

View file

@ -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';

View file

@ -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
}
);
}

View file

@ -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' },
});

View file

@ -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 } });

View file

@ -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

View file

@ -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',

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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[] };

View file

@ -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]: [],