mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
refactor(core): update role in authn route for Hasura (#2944)
This commit is contained in:
parent
b575a45b43
commit
6b5eb4a0e6
5 changed files with 36 additions and 8 deletions
|
@ -1,7 +1,7 @@
|
|||
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
|
||||
import { createMockPool } from 'slonik';
|
||||
|
||||
import { mockResource, mockScope } from '#src/__mocks__/index.js';
|
||||
import { mockResource, mockRole, mockScope } from '#src/__mocks__/index.js';
|
||||
import { mockUser } from '#src/__mocks__/user.js';
|
||||
import { MockQueries } from '#src/test-utils/tenant.js';
|
||||
|
||||
|
@ -16,6 +16,7 @@ const { encryptUserPassword, createUserLibrary } = await import('./user.js');
|
|||
const hasUserWithId = jest.fn();
|
||||
const queries = new MockQueries({
|
||||
users: { hasUserWithId },
|
||||
roles: { findRolesByRoleIds: async () => [mockRole] },
|
||||
scopes: { findScopesByIdsAndResourceId: async () => [mockScope] },
|
||||
usersRoles: { findUsersRolesByUserId: async () => [] },
|
||||
rolesScopes: { findRolesScopesByRoleIds: async () => [] },
|
||||
|
@ -80,3 +81,11 @@ describe('findUserScopesForResourceId()', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findUserRoles()', () => {
|
||||
const { findUserRoles } = createUserLibrary(queries);
|
||||
|
||||
it('returns user roles', async () => {
|
||||
await expect(findUserRoles(mockUser.id)).resolves.toEqual([mockRole]);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -195,6 +195,13 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
return scopes;
|
||||
};
|
||||
|
||||
const findUserRoles = async (userId: string) => {
|
||||
const usersRoles = await findUsersRolesByUserId(userId);
|
||||
const roles = await findRolesByRoleIds(usersRoles.map(({ roleId }) => roleId));
|
||||
|
||||
return roles;
|
||||
};
|
||||
|
||||
return {
|
||||
findUserByIdWithRoles,
|
||||
generateUserId,
|
||||
|
@ -202,5 +209,6 @@ export const createUserLibrary = (queries: Queries) => {
|
|||
checkIdentifierCollision,
|
||||
findUsersByRoleName,
|
||||
findUserScopesForResourceId,
|
||||
findUserRoles,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -43,7 +43,6 @@ type TokenInfo = {
|
|||
sub: string;
|
||||
clientId: unknown;
|
||||
scopes: string[];
|
||||
roleNames?: string[];
|
||||
};
|
||||
|
||||
export const verifyBearerTokenFromRequest = async (
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import { pickDefault, createMockUtils } from '@logto/shared/esm';
|
||||
|
||||
import { mockRole } from '#src/__mocks__/index.js';
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import Libraries from '#src/tenants/Libraries.js';
|
||||
import { MockTenant } from '#src/test-utils/tenant.js';
|
||||
|
||||
const { jest } = import.meta;
|
||||
const { mockEsmWithActual } = createMockUtils(jest);
|
||||
|
@ -12,14 +15,20 @@ const { verifyBearerTokenFromRequest } = await mockEsmWithActual(
|
|||
})
|
||||
);
|
||||
|
||||
const usersLibraries = {
|
||||
findUserRoles: jest.fn(async () => [mockRole]),
|
||||
} satisfies Partial<Libraries['users']>;
|
||||
|
||||
const tenantContext = new MockTenant(undefined, {}, { users: usersLibraries });
|
||||
const { createRequester } = await import('#src/utils/test-utils.js');
|
||||
const request = createRequester({
|
||||
anonymousRoutes: await pickDefault(import('#src/routes/authn.js')),
|
||||
tenantContext,
|
||||
});
|
||||
|
||||
describe('authn route for Hasura', () => {
|
||||
const mockUserId = 'foo';
|
||||
const mockExpectedRole = 'some_role';
|
||||
const mockExpectedRole = mockRole.name;
|
||||
const mockUnauthorizedRole = 'V';
|
||||
const keys = Object.freeze({
|
||||
expectedRole: 'Expected-Role',
|
||||
|
@ -32,7 +41,6 @@ describe('authn route for Hasura', () => {
|
|||
verifyBearerTokenFromRequest.mockResolvedValue({
|
||||
clientId: 'ok',
|
||||
sub: mockUserId,
|
||||
roleNames: [mockExpectedRole],
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -13,8 +13,10 @@ import type { AnonymousRouter, RouterInitArgs } from './types.js';
|
|||
* For now, we only implement the API for Hasura authentication.
|
||||
*/
|
||||
export default function authnRoutes<T extends AnonymousRouter>(
|
||||
...[router, { envSet }]: RouterInitArgs<T>
|
||||
...[router, { envSet, libraries }]: RouterInitArgs<T>
|
||||
) {
|
||||
const { findUserRoles } = libraries.users;
|
||||
|
||||
router.get(
|
||||
'/authn/hasura',
|
||||
koaGuard({
|
||||
|
@ -36,9 +38,11 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
|||
}
|
||||
};
|
||||
|
||||
const { sub, roleNames } = await verifyToken(resource);
|
||||
const { sub } = await verifyToken(resource);
|
||||
const roles = sub ? await findUserRoles(sub) : [];
|
||||
const roleNames = new Set(roles.map(({ name }) => name));
|
||||
|
||||
if (unauthorizedRole && (!expectedRole || !roleNames?.includes(expectedRole))) {
|
||||
if (unauthorizedRole && (!expectedRole || !roleNames.has(expectedRole))) {
|
||||
ctx.body = {
|
||||
'X-Hasura-User-Id':
|
||||
sub ??
|
||||
|
@ -54,7 +58,7 @@ export default function authnRoutes<T extends AnonymousRouter>(
|
|||
|
||||
if (expectedRole) {
|
||||
assertThat(
|
||||
roleNames?.includes(expectedRole),
|
||||
roleNames.has(expectedRole),
|
||||
new RequestError({ code: 'auth.expected_role_not_found', status: 401 })
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue