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 {
|
import { adminTenantId, getManagementApiResourceIndicator } from '@logto/schemas';
|
||||||
adminTenantId,
|
|
||||||
arbitraryObjectGuard,
|
|
||||||
getManagementApiResourceIndicator,
|
|
||||||
} from '@logto/schemas';
|
|
||||||
import Koa from 'koa';
|
import Koa from 'koa';
|
||||||
import Router from 'koa-router';
|
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 type { WithAuthContext } from '#src/middleware/koa-auth/index.js';
|
||||||
import koaAuth 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 koaCors from '#src/middleware/koa-cors.js';
|
||||||
import koaGuard from '#src/middleware/koa-guard.js';
|
|
||||||
import type TenantContext from '#src/tenants/TenantContext.js';
|
import type TenantContext from '#src/tenants/TenantContext.js';
|
||||||
import assertThat from '#src/utils/assert-that.js';
|
import assertThat from '#src/utils/assert-that.js';
|
||||||
|
|
||||||
import socialRoutes from './social.js';
|
import socialRoutes from './social.js';
|
||||||
|
import userRoutes from './user.js';
|
||||||
|
import verificationCodeRoutes from './verification-code.js';
|
||||||
|
|
||||||
export default function initMeApis(tenant: TenantContext): Koa {
|
export default function initMeApis(tenant: TenantContext): Koa {
|
||||||
if (tenant.id !== adminTenantId) {
|
if (tenant.id !== adminTenantId) {
|
||||||
throw new Error('`/me` routes should only be initialized in the admin tenant.');
|
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>();
|
const meRouter = new Router<unknown, WithAuthContext>();
|
||||||
|
|
||||||
meRouter.use(
|
meRouter.use(
|
||||||
|
@ -37,38 +33,9 @@ export default function initMeApis(tenant: TenantContext): Koa {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
meRouter.get('/custom-data', async (ctx, next) => {
|
userRoutes(meRouter, tenant);
|
||||||
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();
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
socialRoutes(meRouter, tenant);
|
socialRoutes(meRouter, tenant);
|
||||||
|
verificationCodeRoutes(meRouter, tenant);
|
||||||
|
|
||||||
const meApp = new Koa();
|
const meApp = new Koa();
|
||||||
meApp.use(koaCors(EnvSet.values.cloudUrlSet));
|
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