mirror of
https://github.com/logto-io/logto.git
synced 2024-12-16 20:26:19 -05:00
feat(core): provide /me
APIs for user password and verification code in cloud AC (#3192)
This commit is contained in:
parent
586cc96113
commit
9311f4cf93
3 changed files with 139 additions and 38 deletions
|
@ -1,8 +1,4 @@
|
|||
import {
|
||||
adminTenantId,
|
||||
arbitraryObjectGuard,
|
||||
getManagementApiResourceIndicator,
|
||||
} from '@logto/schemas';
|
||||
import { adminTenantId, getManagementApiResourceIndicator } from '@logto/schemas';
|
||||
import Koa from 'koa';
|
||||
import Router from 'koa-router';
|
||||
|
||||
|
@ -11,18 +7,18 @@ import RequestError from '#src/errors/RequestError/index.js';
|
|||
import type { WithAuthContext } from '#src/middleware/koa-auth/index.js';
|
||||
import koaAuth from '#src/middleware/koa-auth/index.js';
|
||||
import koaCors from '#src/middleware/koa-cors.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import socialRoutes from './social.js';
|
||||
import userRoutes from './user.js';
|
||||
import verificationCodeRoutes from './verification-code.js';
|
||||
|
||||
export default function initMeApis(tenant: TenantContext): Koa {
|
||||
if (tenant.id !== adminTenantId) {
|
||||
throw new Error('`/me` routes should only be initialized in the admin tenant.');
|
||||
}
|
||||
|
||||
const { findUserById, updateUserById } = tenant.queries.users;
|
||||
const meRouter = new Router<unknown, WithAuthContext>();
|
||||
|
||||
meRouter.use(
|
||||
|
@ -37,38 +33,9 @@ export default function initMeApis(tenant: TenantContext): Koa {
|
|||
}
|
||||
);
|
||||
|
||||
meRouter.get('/custom-data', async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const user = await findUserById(userId);
|
||||
|
||||
ctx.body = user.customData;
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
meRouter.patch(
|
||||
'/custom-data',
|
||||
koaGuard({
|
||||
body: arbitraryObjectGuard,
|
||||
response: arbitraryObjectGuard,
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const { body: customData } = ctx.guard;
|
||||
|
||||
await findUserById(userId);
|
||||
|
||||
const user = await updateUserById(userId, {
|
||||
customData,
|
||||
});
|
||||
|
||||
ctx.body = user.customData;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
userRoutes(meRouter, tenant);
|
||||
socialRoutes(meRouter, tenant);
|
||||
verificationCodeRoutes(meRouter, tenant);
|
||||
|
||||
const meApp = new Koa();
|
||||
meApp.use(koaCors(EnvSet.values.cloudUrlSet));
|
||||
|
|
85
packages/core/src/routes-me/user.ts
Normal file
85
packages/core/src/routes-me/user.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { passwordRegEx } from '@logto/core-kit';
|
||||
import { arbitraryObjectGuard } from '@logto/schemas';
|
||||
import { object, string } from 'zod';
|
||||
|
||||
import RequestError from '#src/errors/RequestError/index.js';
|
||||
import { encryptUserPassword, verifyUserPassword } from '#src/libraries/user.js';
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import assertThat from '#src/utils/assert-that.js';
|
||||
|
||||
import type { RouterInitArgs } from '../routes/types.js';
|
||||
import type { AuthedMeRouter } from './types.js';
|
||||
|
||||
export default function userRoutes<T extends AuthedMeRouter>(
|
||||
...[router, tenant]: RouterInitArgs<T>
|
||||
) {
|
||||
const { findUserById, updateUserById } = tenant.queries.users;
|
||||
|
||||
router.get('/custom-data', async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const user = await findUserById(userId);
|
||||
|
||||
ctx.body = user.customData;
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.patch(
|
||||
'/custom-data',
|
||||
koaGuard({
|
||||
body: arbitraryObjectGuard,
|
||||
response: arbitraryObjectGuard,
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const { body: customData } = ctx.guard;
|
||||
|
||||
await findUserById(userId);
|
||||
|
||||
const user = await updateUserById(userId, {
|
||||
customData,
|
||||
});
|
||||
|
||||
ctx.body = user.customData;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/password/verify',
|
||||
koaGuard({
|
||||
body: object({ password: string().regex(passwordRegEx) }),
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const { password } = ctx.guard.body;
|
||||
|
||||
const user = await findUserById(userId);
|
||||
await verifyUserPassword(user, password);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/password',
|
||||
koaGuard({ body: object({ password: string().regex(passwordRegEx) }) }),
|
||||
async (ctx, next) => {
|
||||
const { id: userId } = ctx.auth;
|
||||
const { password } = ctx.guard.body;
|
||||
|
||||
const user = await findUserById(userId);
|
||||
assertThat(!user.isSuspended, new RequestError({ code: 'user.suspended', status: 401 }));
|
||||
|
||||
const { passwordEncrypted, passwordEncryptionMethod } = await encryptUserPassword(password);
|
||||
await updateUserById(userId, { passwordEncrypted, passwordEncryptionMethod });
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
49
packages/core/src/routes-me/verification-code.ts
Normal file
49
packages/core/src/routes-me/verification-code.ts
Normal file
|
@ -0,0 +1,49 @@
|
|||
import { VerificationCodeType } from '@logto/connector-kit';
|
||||
import {
|
||||
requestVerificationCodePayloadGuard,
|
||||
verifyVerificationCodePayloadGuard,
|
||||
} from '@logto/schemas';
|
||||
|
||||
import koaGuard from '#src/middleware/koa-guard.js';
|
||||
import type { RouterInitArgs } from '#src/routes/types.js';
|
||||
|
||||
import type { AuthedMeRouter } from './types.js';
|
||||
|
||||
export default function verificationCodeRoutes<T extends AuthedMeRouter>(
|
||||
...[router, tenant]: RouterInitArgs<T>
|
||||
) {
|
||||
const codeType = VerificationCodeType.Generic;
|
||||
const {
|
||||
passcodes: { createPasscode, sendPasscode, verifyPasscode },
|
||||
} = tenant.libraries;
|
||||
|
||||
router.post(
|
||||
'/verification-codes',
|
||||
koaGuard({
|
||||
body: requestVerificationCodePayloadGuard,
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const code = await createPasscode(undefined, codeType, ctx.guard.body);
|
||||
await sendPasscode(code);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/verification-codes/verify',
|
||||
koaGuard({
|
||||
body: verifyVerificationCodePayloadGuard,
|
||||
}),
|
||||
async (ctx, next) => {
|
||||
const { verificationCode, ...identifier } = ctx.guard.body;
|
||||
await verifyPasscode(undefined, codeType, verificationCode, identifier);
|
||||
|
||||
ctx.status = 204;
|
||||
|
||||
return next();
|
||||
}
|
||||
);
|
||||
}
|
Loading…
Reference in a new issue