feat(#193): Align authentication mechanism between API and web client
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
- Update AuthUser type in @mosaic/shared to include workspace fields - Update AuthGuard to support both cookie-based and Bearer token authentication - Add /auth/session endpoint for session validation - Install and configure cookie-parser middleware - Update CurrentUser decorator to use shared AuthUser type - Update tests for cookie and token authentication (20 tests passing) This ensures consistent authentication handling across API and web client, with proper type safety and support for both web browsers (cookies) and API clients (Bearer tokens). Fixes #193 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,25 +1,84 @@
|
||||
import { Controller, All, Req, Get, UseGuards } from "@nestjs/common";
|
||||
import type { AuthUser } from "@mosaic/shared";
|
||||
import { Controller, All, Req, Get, UseGuards, Request } from "@nestjs/common";
|
||||
import type { AuthUser, AuthSession } from "@mosaic/shared";
|
||||
import { AuthService } from "./auth.service";
|
||||
import { AuthGuard } from "./guards/auth.guard";
|
||||
import { CurrentUser } from "./decorators/current-user.decorator";
|
||||
|
||||
interface RequestWithSession {
|
||||
user?: AuthUser;
|
||||
session?: {
|
||||
id: string;
|
||||
token: string;
|
||||
expiresAt: Date;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
}
|
||||
|
||||
@Controller("auth")
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
/**
|
||||
* Get current session
|
||||
* Returns user and session data for authenticated user
|
||||
*/
|
||||
@Get("session")
|
||||
@UseGuards(AuthGuard)
|
||||
getSession(@Request() req: RequestWithSession): AuthSession {
|
||||
if (!req.user || !req.session) {
|
||||
// This should never happen after AuthGuard, but TypeScript needs the check
|
||||
throw new Error("User session not found");
|
||||
}
|
||||
|
||||
return {
|
||||
user: req.user,
|
||||
session: {
|
||||
id: req.session.id,
|
||||
token: req.session.token,
|
||||
expiresAt: req.session.expiresAt,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user profile
|
||||
* Returns basic user information
|
||||
*/
|
||||
@Get("profile")
|
||||
@UseGuards(AuthGuard)
|
||||
getProfile(@CurrentUser() user: AuthUser) {
|
||||
return {
|
||||
getProfile(@CurrentUser() user: AuthUser): AuthUser {
|
||||
// Return only defined properties to maintain type safety
|
||||
const profile: AuthUser = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
};
|
||||
|
||||
if (user.image !== undefined) {
|
||||
profile.image = user.image;
|
||||
}
|
||||
if (user.emailVerified !== undefined) {
|
||||
profile.emailVerified = user.emailVerified;
|
||||
}
|
||||
if (user.workspaceId !== undefined) {
|
||||
profile.workspaceId = user.workspaceId;
|
||||
}
|
||||
if (user.currentWorkspaceId !== undefined) {
|
||||
profile.currentWorkspaceId = user.currentWorkspaceId;
|
||||
}
|
||||
if (user.workspaceRole !== undefined) {
|
||||
profile.workspaceRole = user.workspaceRole;
|
||||
}
|
||||
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle all other auth routes (sign-in, sign-up, sign-out, etc.)
|
||||
* Delegates to BetterAuth
|
||||
*/
|
||||
@All("*")
|
||||
async handleAuth(@Req() req: Request) {
|
||||
async handleAuth(@Req() req: Request): Promise<unknown> {
|
||||
const auth = this.authService.getAuth();
|
||||
return auth.handler(req);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user