feat(api): 开发平台、门店相关代码
This commit is contained in:
@@ -3,10 +3,10 @@ import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
Inject,
|
||||
Post,
|
||||
Req,
|
||||
Res,
|
||||
UseGuards,
|
||||
} from '@nestjs/common';
|
||||
import {
|
||||
ApiBearerAuth,
|
||||
@@ -18,12 +18,19 @@ import {
|
||||
ApiUnauthorizedResponse,
|
||||
} from '@nestjs/swagger';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { loginRequestSchema, registerRequestSchema } from '@atlas/contracts';
|
||||
import {
|
||||
loginRequestSchema,
|
||||
registerRequestSchema,
|
||||
type Workspace,
|
||||
} from '@atlas/contracts';
|
||||
import { ApiError } from '../common/api-error.js';
|
||||
import { AccessTokenGuard, type AuthenticatedRequest } from './auth.guard.js';
|
||||
import { Public } from '../common/public.decorator.js';
|
||||
import type { AuthenticatedRequest } from './auth.guard.js';
|
||||
import { AuthService, type RequestMetadata } from './auth.service.js';
|
||||
import { CurrentWorkspace } from '../workspace/current-workspace.decorator.js';
|
||||
|
||||
const refreshCookie = 'atlas_refresh';
|
||||
// 控制器只负责 HTTP 边界;账户、会话与工作空间的一致性由 AuthService 的事务保证。
|
||||
type HttpRequest = {
|
||||
header: (name: string) => string | undefined;
|
||||
ip?: string;
|
||||
@@ -40,9 +47,12 @@ type HttpResponse = {
|
||||
@ApiTags('Authentication')
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
public constructor(private readonly authService: AuthService) {}
|
||||
public constructor(
|
||||
@Inject(AuthService) private readonly authService: AuthService,
|
||||
) {}
|
||||
|
||||
@Post('register')
|
||||
@Public()
|
||||
@ApiOperation({ summary: '注册账户并创建个人工作空间' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
@@ -68,6 +78,8 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Post('login')
|
||||
@Public()
|
||||
@HttpCode(200)
|
||||
@ApiOperation({ summary: '使用邮箱和密码登录' })
|
||||
@ApiBody({
|
||||
schema: {
|
||||
@@ -91,12 +103,15 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Post('refresh')
|
||||
@Public()
|
||||
@HttpCode(200)
|
||||
@ApiOperation({ summary: '轮换 refresh cookie 并获取新的 access token' })
|
||||
public async refresh(
|
||||
@Req() request: HttpRequest,
|
||||
@Res({ passthrough: true }) response: HttpResponse,
|
||||
) {
|
||||
try {
|
||||
// refresh token 不进入 JSON body,仅从浏览器自动发送的 HttpOnly cookie 读取。
|
||||
const result = await this.authService.refresh(
|
||||
readCookie(request.header('cookie'), refreshCookie),
|
||||
metadata(request),
|
||||
@@ -117,7 +132,6 @@ export class AuthController {
|
||||
|
||||
@Post('logout')
|
||||
@HttpCode(204)
|
||||
@UseGuards(AccessTokenGuard)
|
||||
@ApiBearerAuth('access-token')
|
||||
@ApiOperation({ summary: '撤销当前登录会话' })
|
||||
@ApiNoContentResponse({ description: '会话已撤销' })
|
||||
@@ -131,13 +145,17 @@ export class AuthController {
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@UseGuards(AccessTokenGuard)
|
||||
@ApiBearerAuth('access-token')
|
||||
@ApiOperation({ summary: '获取当前用户、工作空间和页头初始上下文' })
|
||||
@ApiOkResponse({ description: '当前身份和工作空间' })
|
||||
@ApiUnauthorizedResponse({ description: 'access token 无效或已过期' })
|
||||
public async me(@Req() request: AuthenticatedRequest) {
|
||||
return { data: await this.authService.me(request.auth.userId) };
|
||||
public async me(
|
||||
@Req() request: AuthenticatedRequest,
|
||||
@CurrentWorkspace() workspace: Workspace,
|
||||
) {
|
||||
return {
|
||||
data: await this.authService.me(request.auth.userId, workspace),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,6 +196,7 @@ function metadata(request: HttpRequest): RequestMetadata {
|
||||
};
|
||||
}
|
||||
function cookieOptions() {
|
||||
// cookie 路径限制在认证接口,避免无关请求携带长期会话凭证。
|
||||
return {
|
||||
httpOnly: true,
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
@@ -198,6 +217,7 @@ function clearRefreshCookie(response: HttpResponse) {
|
||||
});
|
||||
}
|
||||
function readCookie(header: string | undefined, name: string) {
|
||||
// 只解析本服务需要的单个 cookie,避免引入与认证无关的 cookie 状态。
|
||||
return header
|
||||
?.split(';')
|
||||
.map((part) => part.trim())
|
||||
|
||||
Reference in New Issue
Block a user