0
Fork 0
mirror of https://github.com/logto-io/logto.git synced 2025-03-31 22:51:25 -05:00

feat(core): implement oidc getResourceServerInfo (#2875)

This commit is contained in:
wangsijie 2023-01-12 11:49:40 +08:00 committed by GitHub
parent 10ea2307ad
commit 91a5c64e04
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 69 additions and 6 deletions

View file

@ -1,6 +1,8 @@
import { UsersPasswordEncryptionMethod } from '@logto/schemas';
import { createMockPool } from 'slonik';
import { mockResource, mockScope } from '#src/__mocks__/index.js';
import { mockUser } from '#src/__mocks__/user.js';
import { MockQueries } from '#src/test-utils/tenant.js';
const { jest } = import.meta;
@ -12,7 +14,12 @@ const pool = createMockPool({
const { encryptUserPassword, createUserLibrary } = await import('./user.js');
const hasUserWithId = jest.fn();
const queries = new MockQueries({ users: { hasUserWithId } });
const queries = new MockQueries({
users: { hasUserWithId },
scopes: { findScopesByIdsAndResourceId: async () => [mockScope] },
usersRoles: { findUsersRolesByUserId: async () => [] },
rolesScopes: { findRolesScopesByRoleIds: async () => [] },
});
describe('generateUserId()', () => {
const { generateUserId } = createUserLibrary(queries);
@ -63,3 +70,13 @@ describe('encryptUserPassword()', () => {
expect(passwordEncrypted).toContain('argon2');
});
});
describe('findUserScopesForResourceId()', () => {
const { findUserScopesForResourceId } = createUserLibrary(queries);
it('returns scopes that the user has access', async () => {
await expect(findUserScopesForResourceId(mockUser.id, mockResource.id)).resolves.toEqual([
mockScope,
]);
});
});

View file

@ -1,5 +1,5 @@
import { buildIdGenerator } from '@logto/core-kit';
import type { User, CreateUser } from '@logto/schemas';
import type { User, CreateUser, Scope } from '@logto/schemas';
import { Users, UsersPasswordEncryptionMethod } from '@logto/schemas';
import type { OmitAutoSetFields } from '@logto/shared';
import type { Nullable } from '@silverhand/essentials';
@ -51,7 +51,9 @@ export const createUserLibrary = (queries: Queries) => {
pool,
roles: { findRolesByRoleNames, insertRoles, findRoleByRoleName },
users: { hasUser, hasUserWithEmail, hasUserWithId, hasUserWithPhone, findUsersByIds },
usersRoles: { insertUsersRoles, findUsersRolesByRoleId },
usersRoles: { insertUsersRoles, findUsersRolesByRoleId, findUsersRolesByUserId },
rolesScopes: { findRolesScopesByRoleIds },
scopes: { findScopesByIdsAndResourceId },
} = queries;
const generateUserId = async (retries = 500) =>
@ -156,10 +158,25 @@ export const createUserLibrary = (queries: Queries) => {
return findUsersByIds(usersRoles.map(({ userId }) => userId));
};
const findUserScopesForResourceId = async (
userId: string,
resourceId: string
): Promise<readonly Scope[]> => {
const usersRoles = await findUsersRolesByUserId(userId);
const rolesScopes = await findRolesScopesByRoleIds(usersRoles.map(({ roleId }) => roleId));
const scopes = await findScopesByIdsAndResourceId(
rolesScopes.map(({ scopeId }) => scopeId),
resourceId
);
return scopes;
};
return {
generateUserId,
insertUser,
checkIdentifierCollision,
findUsersByRoleName,
findUserScopesForResourceId,
};
};

View file

@ -10,6 +10,7 @@ import snakecaseKeys from 'snakecase-keys';
import envSet from '#src/env-set/index.js';
import { addOidcEventListeners } from '#src/event-listeners/index.js';
import { createUserLibrary } from '#src/libraries/user.js';
import koaAuditLog from '#src/middleware/koa-audit-log.js';
import postgresAdapter from '#src/oidc/adapter.js';
import { isOriginAllowed, validateCustomClientMetadata } from '#src/oidc/utils.js';
@ -36,6 +37,7 @@ export default function initOidc(queries: Queries): Provider {
defaultIdTokenTtl,
defaultRefreshTokenTtl,
} = envSet.oidc;
const { findUserScopesForResourceId } = createUserLibrary(queries);
const logoutSource = readFileSync('static/html/logout.html', 'utf8');
const cookieConfig = Object.freeze({
@ -83,18 +85,22 @@ export default function initOidc(queries: Queries): Provider {
defaultResource: () => '',
// Disable the auto use of authorization_code granted resource feature
useGrantedResource: () => false,
getResourceServerInfo: async (_, indicator) => {
getResourceServerInfo: async (ctx, indicator) => {
const resourceServer = await findResourceByIndicator(indicator);
if (!resourceServer) {
throw new errors.InvalidTarget();
}
const scopes = ctx.oidc.account
? await findUserScopesForResourceId(ctx.oidc.account.accountId, resourceServer.id)
: [];
const { accessTokenTtl: accessTokenTTL } = resourceServer;
return {
accessTokenFormat: 'jwt',
scope: '',
scope: scopes.map(({ name }) => name).join(' '),
accessTokenTTL,
jwt: {
sign: { alg: jwkSigningAlg },

View file

@ -25,6 +25,15 @@ export const createRolesScopesQueries = (pool: CommonQueryMethods) => {
where ${fields.roleId}=${roleId}
`);
const findRolesScopesByRoleIds = async (roleIds: string[]) =>
roleIds.length > 0
? pool.any<RolesScope>(sql`
select ${sql.join(Object.values(fields), sql`,`)}
from ${table}
where ${fields.roleId} in (${sql.join(roleIds, sql`, `)})
`)
: [];
const deleteRolesScope = async (roleId: string, scopeId: string) => {
const { rowCount } = await pool.query(sql`
delete from ${table}
@ -36,5 +45,5 @@ export const createRolesScopesQueries = (pool: CommonQueryMethods) => {
}
};
return { insertRolesScopes, findRolesScopesByRoleId, deleteRolesScope };
return { insertRolesScopes, findRolesScopesByRoleId, findRolesScopesByRoleIds, deleteRolesScope };
};

View file

@ -71,6 +71,19 @@ export const createScopeQueries = (pool: CommonQueryMethods) => {
`)
: [];
const findScopesByIdsAndResourceId = async (
scopeIds: string[],
resourceId: string
): Promise<readonly Scope[]> =>
scopeIds.length > 0
? pool.any<Scope>(sql`
select ${sql.join(Object.values(fields), sql`, `)}
from ${table}
where ${fields.id} in (${sql.join(scopeIds, sql`, `)})
and ${fields.resourceId} = ${resourceId}
`)
: [];
const findScopesByIds = async (scopeIds: string[]) =>
scopeIds.length > 0
? pool.any<Scope>(sql`
@ -109,6 +122,7 @@ export const createScopeQueries = (pool: CommonQueryMethods) => {
findScopesByResourceId,
findScopesByResourceIds,
findScopesByIds,
findScopesByIdsAndResourceId,
insertScope,
findScopeById,
updateScope,