🔒️ feat(auth): centralize JWT validation and add deployment docs

- Migrate Chat, Picture, Presi, Zitare backends to shared auth guards
- Remove duplicate local JWT guards and decorators
- Add CD staging workflow for tagged releases
- Add comprehensive auth architecture documentation
- Add Hetzner deployment and Docker setup guides
- Add environment configuration audit docs
- Update env generation scripts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Wuesteon 2025-12-01 20:44:45 +01:00
parent 942c588e15
commit 5b0b3095ff
50 changed files with 11916 additions and 718 deletions

View file

@ -2,8 +2,7 @@ import { Body, Controller, Get, Post, UseGuards } from '@nestjs/common';
import { isOk } from '@manacore/shared-errors';
import { ChatService } from './chat.service';
import { ChatCompletionDto, ChatCompletionResponseDto } from './dto/chat-completion.dto';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('chat')
@UseGuards(JwtAuthGuard)

View file

@ -1,15 +0,0 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface CurrentUserData {
userId: string;
email: string;
role: string;
sessionId?: string;
}
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): CurrentUserData => {
const request = ctx.switchToHttp().getRequest();
return request.user;
}
);

View file

@ -1,79 +0,0 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
// Development test user ID - used when DEV_BYPASS_AUTH=true
const DEV_USER_ID = '17cb0be7-058a-4964-9e18-1fe7055fd014';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private configService: ConfigService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// Development mode: bypass auth if DEV_BYPASS_AUTH is set
const isDev = this.configService.get<string>('NODE_ENV') === 'development';
const bypassAuth = this.configService.get<string>('DEV_BYPASS_AUTH') === 'true';
if (isDev && bypassAuth) {
// Use test user for development
request.user = {
userId: DEV_USER_ID,
email: 'test@example.com',
role: 'user',
sessionId: 'dev-session',
};
return true;
}
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
try {
// Get Mana Core Auth URL from config
const authUrl =
this.configService.get<string>('MANA_CORE_AUTH_URL') || 'http://localhost:3001';
// Validate token with Mana Core Auth
const response = await fetch(`${authUrl}/api/v1/auth/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
if (!response.ok) {
throw new UnauthorizedException('Invalid token');
}
const { valid, payload } = await response.json();
if (!valid || !payload) {
throw new UnauthorizedException('Invalid token');
}
// Attach user to request
request.user = {
userId: payload.sub,
email: payload.email,
role: payload.role,
sessionId: payload.sessionId,
};
return true;
} catch (error) {
if (error instanceof UnauthorizedException) {
throw error;
}
console.error('Error validating token:', error);
throw new UnauthorizedException('Token validation failed');
}
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}

View file

@ -13,8 +13,7 @@ import { isOk } from '@manacore/shared-errors';
import { ConversationService } from './conversation.service';
import { type Conversation } from '../db/schema/conversations.schema';
import { type Message } from '../db/schema/messages.schema';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('conversations')
@UseGuards(JwtAuthGuard)

View file

@ -2,8 +2,7 @@ import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/c
import { isOk } from '@manacore/shared-errors';
import { DocumentService } from './document.service';
import { type Document } from '../db/schema/documents.schema';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('documents')
@UseGuards(JwtAuthGuard)

View file

@ -6,16 +6,18 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS for mobile and web apps
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5174',
'http://localhost:5178',
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001',
];
app.enableCors({
origin: [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5174', // Chat web app (dev server port)
'http://localhost:5178', // Chat web app (alternative)
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001', // Mana Core Auth
],
origin: corsOrigins,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
credentials: true,
});
@ -33,7 +35,7 @@ async function bootstrap() {
);
// Set global prefix for API routes
app.setGlobalPrefix('api');
app.setGlobalPrefix('api/v1');
const port = process.env.PORT || 3002;
await app.listen(port);

View file

@ -2,8 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@n
import { isOk } from '@manacore/shared-errors';
import { SpaceService } from './space.service';
import { type Space, type SpaceMember } from '../db/schema/spaces.schema';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('spaces')
@UseGuards(JwtAuthGuard)

View file

@ -2,8 +2,7 @@ import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@n
import { isOk } from '@manacore/shared-errors';
import { TemplateService } from './template.service';
import { type Template } from '../db/schema/templates.schema';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('templates')
@UseGuards(JwtAuthGuard)

View file

@ -36,7 +36,7 @@ async function bootstrap() {
});
// Global prefix
app.setGlobalPrefix('v1', {
app.setGlobalPrefix('api/v1', {
exclude: ['health', 'health/ready', 'health/live'],
});

View file

@ -1,7 +1,6 @@
import { Controller, Get, Post, Delete, Param, Query, Body, UseGuards } from '@nestjs/common';
import { BatchService } from './batch.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { CreateBatchDto, GetBatchQueryDto } from './dto/batch.dto';
@Controller('batch')

View file

@ -1,7 +1,6 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { BoardItemService } from './board-item.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import {
AddImageToBoardDto,
AddTextToBoardDto,

View file

@ -10,8 +10,7 @@ import {
UseGuards,
} from '@nestjs/common';
import { BoardService } from './board.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import {
CreateBoardDto,
UpdateBoardDto,

View file

@ -1,15 +0,0 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export interface CurrentUserData {
userId: string;
email: string;
role: string;
sessionId?: string;
}
export const CurrentUser = createParamDecorator(
(data: unknown, ctx: ExecutionContext): CurrentUserData => {
const request = ctx.switchToHttp().getRequest();
return request.user;
}
);

View file

@ -1,60 +0,0 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(private configService: ConfigService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
try {
// Get Mana Core Auth URL from config
const authUrl =
this.configService.get<string>('MANA_CORE_AUTH_URL') || 'http://localhost:3001';
// Validate token with Mana Core Auth
const response = await fetch(`${authUrl}/api/v1/auth/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
if (!response.ok) {
throw new UnauthorizedException('Invalid token');
}
const { valid, payload } = await response.json();
if (!valid || !payload) {
throw new UnauthorizedException('Invalid token');
}
// Attach user to request
request.user = {
userId: payload.sub,
email: payload.email,
role: payload.role,
sessionId: payload.sessionId,
};
return true;
} catch (error) {
if (error instanceof UnauthorizedException) {
throw error;
}
console.error('Error validating token:', error);
throw new UnauthorizedException('Token validation failed');
}
}
private extractTokenFromHeader(request: any): string | undefined {
const [type, token] = request.headers.authorization?.split(' ') ?? [];
return type === 'Bearer' ? token : undefined;
}
}

View file

@ -1,6 +1,6 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ExploreService } from './explore.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { JwtAuthGuard } from '@manacore/shared-nestjs-auth';
import { GetPublicImagesDto, SearchPublicImagesDto } from './dto/explore.dto';
@Controller('explore')

View file

@ -1,7 +1,6 @@
import { Controller, Get, Post, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { GenerateService } from './generate.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { GenerateImageDto } from './dto/generate.dto';
@Controller('generate')

View file

@ -10,8 +10,7 @@ import {
UseGuards,
} from '@nestjs/common';
import { ImageService } from './image.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { GetImagesQueryDto, ToggleFavoriteDto, BatchImageIdsDto } from './dto/image.dto';
@Controller('images')

View file

@ -8,14 +8,14 @@ async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
// Enable CORS for mobile and web apps
const allowedOrigins = [
const allowedOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5174',
'http://localhost:5175',
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001', // Mana Core Auth
'http://localhost:3001',
];
app.enableCors({
@ -47,9 +47,9 @@ async function bootstrap() {
});
// Set global prefix for API routes
app.setGlobalPrefix('api');
app.setGlobalPrefix('api/v1');
const port = process.env.PORT || 3003;
const port = process.env.PORT || 3006;
await app.listen(port);
console.log(`Picture backend running on http://localhost:${port}`);
}

View file

@ -1,7 +1,6 @@
import { Controller, Get, Patch, Body, UseGuards } from '@nestjs/common';
import { ProfileService } from './profile.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
import { UpdateProfileDto, ProfileResponse, UserStatsResponse, RateLimitsResponse } from './dto/profile.dto';
@Controller('profiles')

View file

@ -1,6 +1,6 @@
import { Controller, Get, Post, Patch, Delete, Param, Body, UseGuards } from '@nestjs/common';
import { TagService } from './tag.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { JwtAuthGuard } from '@manacore/shared-nestjs-auth';
import { CreateTagDto, UpdateTagDto } from './dto/tag.dto';
@Controller('tags')

View file

@ -11,8 +11,7 @@ import {
} from '@nestjs/common';
import { FileInterceptor, FilesInterceptor } from '@nestjs/platform-express';
import { UploadService } from './upload.service';
import { JwtAuthGuard } from '../common/guards/jwt-auth.guard';
import { CurrentUser, CurrentUserData } from '../common/decorators/current-user.decorator';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_MIME_TYPES = ['image/jpeg', 'image/png', 'image/webp'];

View file

@ -16,6 +16,7 @@
"db:seed": "tsx src/db/seed.ts"
},
"dependencies": {
"@manacore/shared-nestjs-auth": "workspace:*",
"@nestjs/common": "^10.4.15",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.15",

View file

@ -1,84 +0,0 @@
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
/**
* JWT Auth Guard - Validates tokens via Mana Core Auth service
*
* Uses Better Auth with EdDSA algorithm (not RS256).
* Validates tokens by calling the central auth service's /validate endpoint.
*/
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private configService: ConfigService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
// Development mode: bypass auth if DEV_BYPASS_AUTH is set
const isDev = this.configService.get<string>('NODE_ENV') === 'development';
const bypassAuth = this.configService.get<string>('DEV_BYPASS_AUTH') === 'true';
if (isDev && bypassAuth) {
request.user = {
sub: '00000000-0000-0000-0000-000000000000',
email: 'dev@example.com',
role: 'user',
};
return true;
}
const token = this.extractTokenFromHeader(request);
if (!token) {
throw new UnauthorizedException('No token provided');
}
try {
// Get Mana Core Auth URL from config
const authUrl =
this.configService.get<string>('MANA_CORE_AUTH_URL') || 'http://localhost:3001';
// Validate token with Mana Core Auth
const response = await fetch(`${authUrl}/api/v1/auth/validate`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
});
if (!response.ok) {
throw new UnauthorizedException('Invalid token');
}
const { valid, payload } = await response.json();
if (!valid || !payload) {
throw new UnauthorizedException('Invalid token');
}
// Attach user to request
request.user = {
sub: payload.sub,
email: payload.email,
role: payload.role,
sessionId: payload.sessionId || payload.sid,
};
return true;
} catch (error) {
if (error instanceof UnauthorizedException) {
throw error;
}
console.error('[AuthGuard] Error validating token:', error);
throw new UnauthorizedException('Token validation failed');
}
}
private extractTokenFromHeader(request: any): string | undefined {
const authHeader = request.headers.authorization;
if (!authHeader) {
return undefined;
}
const [type, token] = authHeader.split(' ');
return type === 'Bearer' ? token : undefined;
}
}

View file

@ -7,43 +7,42 @@ import {
Body,
Param,
UseGuards,
Request,
} from '@nestjs/common';
import { DeckService } from './deck.service';
import { CreateDeckDto, UpdateDeckDto } from './deck.dto';
import { AuthGuard } from '../auth/auth.guard';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('decks')
@UseGuards(AuthGuard)
@UseGuards(JwtAuthGuard)
export class DeckController {
constructor(private readonly deckService: DeckService) {}
@Get()
async findAll(@Request() req: { user: { sub: string } }) {
return this.deckService.findByUser(req.user.sub);
async findAll(@CurrentUser() user: CurrentUserData) {
return this.deckService.findByUser(user.userId);
}
@Get(':id')
async findOne(@Param('id') id: string, @Request() req: { user: { sub: string } }) {
return this.deckService.findOneWithSlides(id, req.user.sub);
async findOne(@Param('id') id: string, @CurrentUser() user: CurrentUserData) {
return this.deckService.findOneWithSlides(id, user.userId);
}
@Post()
async create(@Body() createDeckDto: CreateDeckDto, @Request() req: { user: { sub: string } }) {
return this.deckService.create(req.user.sub, createDeckDto);
async create(@Body() createDeckDto: CreateDeckDto, @CurrentUser() user: CurrentUserData) {
return this.deckService.create(user.userId, createDeckDto);
}
@Put(':id')
async update(
@Param('id') id: string,
@Body() updateDeckDto: UpdateDeckDto,
@Request() req: { user: { sub: string } }
@CurrentUser() user: CurrentUserData
) {
return this.deckService.update(id, req.user.sub, updateDeckDto);
return this.deckService.update(id, user.userId, updateDeckDto);
}
@Delete(':id')
async remove(@Param('id') id: string, @Request() req: { user: { sub: string } }) {
return this.deckService.remove(id, req.user.sub);
async remove(@Param('id') id: string, @CurrentUser() user: CurrentUserData) {
return this.deckService.remove(id, user.userId);
}
}

View file

@ -6,16 +6,18 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS for mobile and web apps
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5177',
'http://localhost:5178',
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001',
];
app.enableCors({
origin: [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5177',
'http://localhost:5178', // Presi web app
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001', // Mana Core Auth
],
origin: corsOrigins,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials: true,
});
@ -30,7 +32,7 @@ async function bootstrap() {
);
// Set global prefix for API routes
app.setGlobalPrefix('api');
app.setGlobalPrefix('api/v1');
const port = process.env.PORT || 3008;
await app.listen(port);

View file

@ -1,7 +1,7 @@
import { Controller, Get, Post, Delete, Body, Param, UseGuards, Request } from '@nestjs/common';
import { Controller, Get, Post, Delete, Body, Param, UseGuards } from '@nestjs/common';
import { ShareService } from './share.service';
import { CreateShareDto } from './share.dto';
import { AuthGuard } from '../auth/auth.guard';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller('share')
export class ShareController {
@ -15,28 +15,28 @@ export class ShareController {
// Authenticated endpoints
@Post('deck/:deckId')
@UseGuards(AuthGuard)
@UseGuards(JwtAuthGuard)
async createShare(
@Param('deckId') deckId: string,
@Body() createShareDto: CreateShareDto,
@Request() req: { user: { sub: string } }
@CurrentUser() user: CurrentUserData
) {
const expiresAt = createShareDto.expiresAt ? new Date(createShareDto.expiresAt) : undefined;
return this.shareService.createShare(deckId, req.user.sub, expiresAt);
return this.shareService.createShare(deckId, user.userId, expiresAt);
}
@Get('deck/:deckId/links')
@UseGuards(AuthGuard)
@UseGuards(JwtAuthGuard)
async getSharesForDeck(
@Param('deckId') deckId: string,
@Request() req: { user: { sub: string } }
@CurrentUser() user: CurrentUserData
) {
return this.shareService.getSharesForDeck(deckId, req.user.sub);
return this.shareService.getSharesForDeck(deckId, user.userId);
}
@Delete(':shareId')
@UseGuards(AuthGuard)
async deleteShare(@Param('shareId') shareId: string, @Request() req: { user: { sub: string } }) {
return this.shareService.deleteShare(shareId, req.user.sub);
@UseGuards(JwtAuthGuard)
async deleteShare(@Param('shareId') shareId: string, @CurrentUser() user: CurrentUserData) {
return this.shareService.deleteShare(shareId, user.userId);
}
}

View file

@ -1,10 +1,10 @@
import { Controller, Post, Put, Delete, Body, Param, UseGuards, Request } from '@nestjs/common';
import { Controller, Post, Put, Delete, Body, Param, UseGuards } from '@nestjs/common';
import { SlideService } from './slide.service';
import { CreateSlideDto, UpdateSlideDto, ReorderSlidesDto } from './slide.dto';
import { AuthGuard } from '../auth/auth.guard';
import { JwtAuthGuard, CurrentUser, CurrentUserData } from '@manacore/shared-nestjs-auth';
@Controller()
@UseGuards(AuthGuard)
@UseGuards(JwtAuthGuard)
export class SlideController {
constructor(private readonly slideService: SlideService) {}
@ -12,27 +12,27 @@ export class SlideController {
async create(
@Param('deckId') deckId: string,
@Body() createSlideDto: CreateSlideDto,
@Request() req: { user: { sub: string } }
@CurrentUser() user: CurrentUserData
) {
return this.slideService.create(deckId, req.user.sub, createSlideDto);
return this.slideService.create(deckId, user.userId, createSlideDto);
}
@Put('slides/:id')
async update(
@Param('id') id: string,
@Body() updateSlideDto: UpdateSlideDto,
@Request() req: { user: { sub: string } }
@CurrentUser() user: CurrentUserData
) {
return this.slideService.update(id, req.user.sub, updateSlideDto);
return this.slideService.update(id, user.userId, updateSlideDto);
}
@Delete('slides/:id')
async remove(@Param('id') id: string, @Request() req: { user: { sub: string } }) {
return this.slideService.remove(id, req.user.sub);
async remove(@Param('id') id: string, @CurrentUser() user: CurrentUserData) {
return this.slideService.remove(id, user.userId);
}
@Put('slides/reorder')
async reorder(@Body() reorderDto: ReorderSlidesDto, @Request() req: { user: { sub: string } }) {
return this.slideService.reorder(req.user.sub, reorderDto);
async reorder(@Body() reorderDto: ReorderSlidesDto, @CurrentUser() user: CurrentUserData) {
return this.slideService.reorder(user.userId, reorderDto);
}
}

View file

@ -6,15 +6,17 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Enable CORS for mobile and web apps
const corsOrigins = process.env.CORS_ORIGINS?.split(',').map((origin) => origin.trim()) || [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5177',
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001',
];
app.enableCors({
origin: [
'http://localhost:3000',
'http://localhost:5173',
'http://localhost:5177',
'http://localhost:8081',
'exp://localhost:8081',
'http://localhost:3001', // Mana Core Auth
],
origin: corsOrigins,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
credentials: true,
});
@ -29,7 +31,7 @@ async function bootstrap() {
);
// Set global prefix for API routes
app.setGlobalPrefix('api');
app.setGlobalPrefix('api/v1');
const port = process.env.PORT || 3007;
await app.listen(port);