wip
This commit is contained in:
11
apps/website/app/api/auth/logout/route.ts
Normal file
11
apps/website/app/api/auth/logout/route.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAuthService } from '@/lib/auth';
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const authService = getAuthService();
|
||||
await authService.logout();
|
||||
|
||||
const url = new URL(request.url);
|
||||
const redirectUrl = new URL('/', url.origin);
|
||||
return NextResponse.redirect(redirectUrl);
|
||||
}
|
||||
11
apps/website/app/api/auth/session/route.ts
Normal file
11
apps/website/app/api/auth/session/route.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
import { getAuthService } from '@/lib/auth';
|
||||
|
||||
export async function GET() {
|
||||
const authService = getAuthService();
|
||||
const session = await authService.getCurrentSession();
|
||||
|
||||
return NextResponse.json({
|
||||
session,
|
||||
});
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import { NextResponse } from 'next/server';
|
||||
|
||||
import { getAuthService } from '../../../../lib/auth';
|
||||
|
||||
const SESSION_COOKIE = 'gp_demo_session';
|
||||
const STATE_COOKIE = 'gp_demo_auth_state';
|
||||
|
||||
export async function GET(request: Request) {
|
||||
@@ -24,14 +23,7 @@ export async function GET(request: Request) {
|
||||
}
|
||||
|
||||
const authService = getAuthService();
|
||||
const session = await authService.loginWithIracingCallback({ code, state, returnTo });
|
||||
|
||||
cookieStore.set(SESSION_COOKIE, JSON.stringify(session), {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
await authService.loginWithIracingCallback({ code, state, returnTo });
|
||||
|
||||
cookieStore.delete(STATE_COOKIE);
|
||||
|
||||
|
||||
@@ -1,17 +1,8 @@
|
||||
export interface AuthUser {
|
||||
id: string;
|
||||
displayName: string;
|
||||
iracingCustomerId?: string;
|
||||
primaryDriverId?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
import type { AuthenticatedUserDTO } from '@gridpilot/identity/application/dto/AuthenticatedUserDTO';
|
||||
import type { AuthSessionDTO } from '@gridpilot/identity/application/dto/AuthSessionDTO';
|
||||
|
||||
export interface AuthSession {
|
||||
user: AuthUser;
|
||||
issuedAt: number;
|
||||
expiresAt: number;
|
||||
token: string;
|
||||
}
|
||||
export type AuthUser = AuthenticatedUserDTO;
|
||||
export type AuthSession = AuthSessionDTO;
|
||||
|
||||
export interface AuthService {
|
||||
getCurrentSession(): Promise<AuthSession | null>;
|
||||
|
||||
@@ -1,59 +1,32 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
import type { AuthService, AuthSession, AuthUser } from './AuthService';
|
||||
import { createStaticRacingSeed } from '@gridpilot/testing-support';
|
||||
|
||||
const SESSION_COOKIE = 'gp_demo_session';
|
||||
const STATE_COOKIE = 'gp_demo_auth_state';
|
||||
|
||||
function parseCookieValue(raw: string | undefined): AuthSession | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as AuthSession;
|
||||
if (!parsed.expiresAt || Date.now() > parsed.expiresAt) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function serializeSession(session: AuthSession): string {
|
||||
return JSON.stringify(session);
|
||||
}
|
||||
import type { AuthService, AuthSession } from './AuthService';
|
||||
import type { AuthCallbackCommandDTO } from '@gridpilot/identity/application/dto/AuthCallbackCommandDTO';
|
||||
import type { StartAuthCommandDTO } from '@gridpilot/identity/application/dto/StartAuthCommandDTO';
|
||||
import { StartAuthUseCase } from '@gridpilot/identity/application/use-cases/StartAuthUseCase';
|
||||
import { GetCurrentUserSessionUseCase } from '@gridpilot/identity/application/use-cases/GetCurrentUserSessionUseCase';
|
||||
import { HandleAuthCallbackUseCase } from '@gridpilot/identity/application/use-cases/HandleAuthCallbackUseCase';
|
||||
import { LogoutUseCase } from '@gridpilot/identity/application/use-cases/LogoutUseCase';
|
||||
import { CookieIdentitySessionAdapter } from '@gridpilot/identity/infrastructure/session/CookieIdentitySessionAdapter';
|
||||
import { IracingDemoIdentityProviderAdapter } from '@gridpilot/identity/infrastructure/providers/IracingDemoIdentityProviderAdapter';
|
||||
|
||||
export class InMemoryAuthService implements AuthService {
|
||||
private readonly seedDriverId: string;
|
||||
|
||||
constructor() {
|
||||
const seed = createStaticRacingSeed(42);
|
||||
this.seedDriverId = seed.drivers[0]?.id ?? 'driver-1';
|
||||
}
|
||||
|
||||
async getCurrentSession(): Promise<AuthSession | null> {
|
||||
const store = await cookies();
|
||||
const raw = store.get(SESSION_COOKIE)?.value;
|
||||
return parseCookieValue(raw);
|
||||
const sessionPort = new CookieIdentitySessionAdapter();
|
||||
const useCase = new GetCurrentUserSessionUseCase(sessionPort);
|
||||
return useCase.execute();
|
||||
}
|
||||
|
||||
async startIracingAuthRedirect(
|
||||
returnTo?: string,
|
||||
): Promise<{ redirectUrl: string; state: string }> {
|
||||
const state = randomUUID();
|
||||
const provider = new IracingDemoIdentityProviderAdapter();
|
||||
const useCase = new StartAuthUseCase(provider);
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('code', 'dummy-code');
|
||||
params.set('state', state);
|
||||
if (returnTo) {
|
||||
params.set('returnTo', returnTo);
|
||||
}
|
||||
|
||||
return {
|
||||
redirectUrl: `/auth/iracing/callback?${params.toString()}`,
|
||||
state,
|
||||
const command: StartAuthCommandDTO = {
|
||||
provider: 'IRACING_DEMO',
|
||||
returnTo,
|
||||
};
|
||||
|
||||
return useCase.execute(command);
|
||||
}
|
||||
|
||||
async loginWithIracingCallback(params: {
|
||||
@@ -61,36 +34,23 @@ export class InMemoryAuthService implements AuthService {
|
||||
state: string;
|
||||
returnTo?: string;
|
||||
}): Promise<AuthSession> {
|
||||
if (!params.code) {
|
||||
throw new Error('Missing auth code');
|
||||
}
|
||||
if (!params.state) {
|
||||
throw new Error('Missing auth state');
|
||||
}
|
||||
const provider = new IracingDemoIdentityProviderAdapter();
|
||||
const sessionPort = new CookieIdentitySessionAdapter();
|
||||
const useCase = new HandleAuthCallbackUseCase(provider, sessionPort);
|
||||
|
||||
const user: AuthUser = {
|
||||
id: 'demo-user',
|
||||
displayName: 'GridPilot Demo Driver',
|
||||
iracingCustomerId: '000000',
|
||||
primaryDriverId: this.seedDriverId,
|
||||
avatarUrl: `/api/avatar/${this.seedDriverId}`,
|
||||
const command: AuthCallbackCommandDTO = {
|
||||
provider: 'IRACING_DEMO',
|
||||
code: params.code,
|
||||
state: params.state,
|
||||
returnTo: params.returnTo,
|
||||
};
|
||||
|
||||
const now = Date.now();
|
||||
const expiresAt = now + 24 * 60 * 60 * 1000;
|
||||
|
||||
const session: AuthSession = {
|
||||
user,
|
||||
issuedAt: now,
|
||||
expiresAt,
|
||||
token: randomUUID(),
|
||||
};
|
||||
|
||||
return session;
|
||||
return useCase.execute(command);
|
||||
}
|
||||
|
||||
async logout(): Promise<void> {
|
||||
// Intentionally does nothing; cookie deletion is handled by route handlers.
|
||||
return;
|
||||
const sessionPort = new CookieIdentitySessionAdapter();
|
||||
const useCase = new LogoutUseCase(sessionPort);
|
||||
await useCase.execute();
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,11 @@
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"@/*": ["./*"]
|
||||
"@/*": ["./*"],
|
||||
"@gridpilot/identity/*": ["../../packages/identity/*"],
|
||||
"@gridpilot/racing/*": ["../../packages/racing/*"],
|
||||
"@gridpilot/social/*": ["../../packages/social/*"],
|
||||
"@gridpilot/testing-support": ["../../packages/testing-support"]
|
||||
}
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { AuthProviderDTO } from './AuthProviderDTO';
|
||||
|
||||
export interface AuthCallbackCommandDTO {
|
||||
provider: AuthProviderDTO;
|
||||
code: string;
|
||||
state: string;
|
||||
returnTo?: string;
|
||||
}
|
||||
1
packages/identity/application/dto/AuthProviderDTO.ts
Normal file
1
packages/identity/application/dto/AuthProviderDTO.ts
Normal file
@@ -0,0 +1 @@
|
||||
export type AuthProviderDTO = 'IRACING_DEMO';
|
||||
8
packages/identity/application/dto/AuthSessionDTO.ts
Normal file
8
packages/identity/application/dto/AuthSessionDTO.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import type { AuthenticatedUserDTO } from './AuthenticatedUserDTO';
|
||||
|
||||
export interface AuthSessionDTO {
|
||||
user: AuthenticatedUserDTO;
|
||||
issuedAt: number;
|
||||
expiresAt: number;
|
||||
token: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
export interface AuthenticatedUserDTO {
|
||||
id: string;
|
||||
displayName: string;
|
||||
email?: string;
|
||||
iracingCustomerId?: string;
|
||||
primaryDriverId?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
4
packages/identity/application/dto/IracingAuthStateDTO.ts
Normal file
4
packages/identity/application/dto/IracingAuthStateDTO.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export interface IracingAuthStateDTO {
|
||||
state: string;
|
||||
returnTo?: string;
|
||||
}
|
||||
6
packages/identity/application/dto/StartAuthCommandDTO.ts
Normal file
6
packages/identity/application/dto/StartAuthCommandDTO.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import type { AuthProviderDTO } from './AuthProviderDTO';
|
||||
|
||||
export interface StartAuthCommandDTO {
|
||||
provider: AuthProviderDTO;
|
||||
returnTo?: string;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
|
||||
import type { AuthCallbackCommandDTO } from '../dto/AuthCallbackCommandDTO';
|
||||
import type { StartAuthCommandDTO } from '../dto/StartAuthCommandDTO';
|
||||
|
||||
export interface IdentityProviderPort {
|
||||
startAuth(command: StartAuthCommandDTO): Promise<{ redirectUrl: string; state: string }>;
|
||||
completeAuth(command: AuthCallbackCommandDTO): Promise<AuthenticatedUserDTO>;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
|
||||
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
|
||||
|
||||
export interface IdentitySessionPort {
|
||||
getCurrentSession(): Promise<AuthSessionDTO | null>;
|
||||
createSession(user: AuthenticatedUserDTO): Promise<AuthSessionDTO>;
|
||||
clearSession(): Promise<void>;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
|
||||
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||
|
||||
export class GetCurrentUserSessionUseCase {
|
||||
private readonly sessionPort: IdentitySessionPort;
|
||||
|
||||
constructor(sessionPort: IdentitySessionPort) {
|
||||
this.sessionPort = sessionPort;
|
||||
}
|
||||
|
||||
async execute(): Promise<AuthSessionDTO | null> {
|
||||
return this.sessionPort.getCurrentSession();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import type { AuthCallbackCommandDTO } from '../dto/AuthCallbackCommandDTO';
|
||||
import type { AuthSessionDTO } from '../dto/AuthSessionDTO';
|
||||
import type { AuthenticatedUserDTO } from '../dto/AuthenticatedUserDTO';
|
||||
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
|
||||
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||
|
||||
export class HandleAuthCallbackUseCase {
|
||||
private readonly provider: IdentityProviderPort;
|
||||
private readonly sessionPort: IdentitySessionPort;
|
||||
|
||||
constructor(provider: IdentityProviderPort, sessionPort: IdentitySessionPort) {
|
||||
this.provider = provider;
|
||||
this.sessionPort = sessionPort;
|
||||
}
|
||||
|
||||
async execute(command: AuthCallbackCommandDTO): Promise<AuthSessionDTO> {
|
||||
const user: AuthenticatedUserDTO = await this.provider.completeAuth(command);
|
||||
const session = await this.sessionPort.createSession(user);
|
||||
return session;
|
||||
}
|
||||
}
|
||||
13
packages/identity/application/use-cases/LogoutUseCase.ts
Normal file
13
packages/identity/application/use-cases/LogoutUseCase.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import type { IdentitySessionPort } from '../ports/IdentitySessionPort';
|
||||
|
||||
export class LogoutUseCase {
|
||||
private readonly sessionPort: IdentitySessionPort;
|
||||
|
||||
constructor(sessionPort: IdentitySessionPort) {
|
||||
this.sessionPort = sessionPort;
|
||||
}
|
||||
|
||||
async execute(): Promise<void> {
|
||||
await this.sessionPort.clearSession();
|
||||
}
|
||||
}
|
||||
14
packages/identity/application/use-cases/StartAuthUseCase.ts
Normal file
14
packages/identity/application/use-cases/StartAuthUseCase.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import type { StartAuthCommandDTO } from '../dto/StartAuthCommandDTO';
|
||||
import type { IdentityProviderPort } from '../ports/IdentityProviderPort';
|
||||
|
||||
export class StartAuthUseCase {
|
||||
private readonly provider: IdentityProviderPort;
|
||||
|
||||
constructor(provider: IdentityProviderPort) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
async execute(command: StartAuthCommandDTO): Promise<{ redirectUrl: string; state: string }> {
|
||||
return this.provider.startAuth(command);
|
||||
}
|
||||
}
|
||||
73
packages/identity/domain/entities/User.ts
Normal file
73
packages/identity/domain/entities/User.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import type { EmailValidationResult } from '../value-objects/EmailAddress';
|
||||
import { validateEmail } from '../value-objects/EmailAddress';
|
||||
import { UserId } from '../value-objects/UserId';
|
||||
|
||||
export interface UserProps {
|
||||
id: UserId;
|
||||
displayName: string;
|
||||
email?: string;
|
||||
iracingCustomerId?: string;
|
||||
primaryDriverId?: string;
|
||||
avatarUrl?: string;
|
||||
}
|
||||
|
||||
export class User {
|
||||
private readonly id: UserId;
|
||||
private displayName: string;
|
||||
private email?: string;
|
||||
private iracingCustomerId?: string;
|
||||
private primaryDriverId?: string;
|
||||
private avatarUrl?: string;
|
||||
|
||||
private constructor(props: UserProps) {
|
||||
if (!props.displayName || !props.displayName.trim()) {
|
||||
throw new Error('User displayName cannot be empty');
|
||||
}
|
||||
|
||||
this.id = props.id;
|
||||
this.displayName = props.displayName.trim();
|
||||
this.email = props.email;
|
||||
this.iracingCustomerId = props.iracingCustomerId;
|
||||
this.primaryDriverId = props.primaryDriverId;
|
||||
this.avatarUrl = props.avatarUrl;
|
||||
}
|
||||
|
||||
public static create(props: UserProps): User {
|
||||
if (props.email) {
|
||||
const result: EmailValidationResult = validateEmail(props.email);
|
||||
if (!result.success) {
|
||||
throw new Error(result.error);
|
||||
}
|
||||
return new User({
|
||||
...props,
|
||||
email: result.email,
|
||||
});
|
||||
}
|
||||
|
||||
return new User(props);
|
||||
}
|
||||
|
||||
public getId(): UserId {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
public getDisplayName(): string {
|
||||
return this.displayName;
|
||||
}
|
||||
|
||||
public getEmail(): string | undefined {
|
||||
return this.email;
|
||||
}
|
||||
|
||||
public getIracingCustomerId(): string | undefined {
|
||||
return this.iracingCustomerId;
|
||||
}
|
||||
|
||||
public getPrimaryDriverId(): string | undefined {
|
||||
return this.primaryDriverId;
|
||||
}
|
||||
|
||||
public getAvatarUrl(): string | undefined {
|
||||
return this.avatarUrl;
|
||||
}
|
||||
}
|
||||
22
packages/identity/domain/value-objects/UserId.ts
Normal file
22
packages/identity/domain/value-objects/UserId.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
export class UserId {
|
||||
private readonly value: string;
|
||||
|
||||
private constructor(value: string) {
|
||||
if (!value || !value.trim()) {
|
||||
throw new Error('UserId cannot be empty');
|
||||
}
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public static fromString(value: string): UserId {
|
||||
return new UserId(value);
|
||||
}
|
||||
|
||||
public toString(): string {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public equals(other: UserId): boolean {
|
||||
return this.value === other.value;
|
||||
}
|
||||
}
|
||||
@@ -1 +1,15 @@
|
||||
export * from './domain/value-objects/EmailAddress';
|
||||
export * from './domain/value-objects/EmailAddress';
|
||||
export * from './domain/value-objects/UserId';
|
||||
export * from './domain/entities/User';
|
||||
|
||||
export * from './application/dto/AuthenticatedUserDTO';
|
||||
export * from './application/dto/AuthSessionDTO';
|
||||
export * from './application/dto/AuthCallbackCommandDTO';
|
||||
export * from './application/dto/StartAuthCommandDTO';
|
||||
export * from './application/dto/AuthProviderDTO';
|
||||
export * from './application/dto/IracingAuthStateDTO';
|
||||
|
||||
export * from './application/use-cases/StartAuthUseCase';
|
||||
export * from './application/use-cases/HandleAuthCallbackUseCase';
|
||||
export * from './application/use-cases/GetCurrentUserSessionUseCase';
|
||||
export * from './application/use-cases/LogoutUseCase';
|
||||
@@ -0,0 +1,50 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { createStaticRacingSeed } from '../../../testing-support';
|
||||
import type { IdentityProviderPort } from '../../application/ports/IdentityProviderPort';
|
||||
import type { StartAuthCommandDTO } from '../../application/dto/StartAuthCommandDTO';
|
||||
import type { AuthCallbackCommandDTO } from '../../application/dto/AuthCallbackCommandDTO';
|
||||
import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO';
|
||||
|
||||
export class IracingDemoIdentityProviderAdapter implements IdentityProviderPort {
|
||||
private readonly seedDriverId: string;
|
||||
|
||||
constructor() {
|
||||
const seed = createStaticRacingSeed(42);
|
||||
this.seedDriverId = seed.drivers[0]?.id ?? 'driver-1';
|
||||
}
|
||||
|
||||
async startAuth(command: StartAuthCommandDTO): Promise<{ redirectUrl: string; state: string }> {
|
||||
const state = randomUUID();
|
||||
|
||||
const params = new URLSearchParams();
|
||||
params.set('code', 'dummy-code');
|
||||
params.set('state', state);
|
||||
if (command.returnTo) {
|
||||
params.set('returnTo', command.returnTo);
|
||||
}
|
||||
|
||||
return {
|
||||
redirectUrl: `/auth/iracing/callback?${params.toString()}`,
|
||||
state,
|
||||
};
|
||||
}
|
||||
|
||||
async completeAuth(command: AuthCallbackCommandDTO): Promise<AuthenticatedUserDTO> {
|
||||
if (!command.code) {
|
||||
throw new Error('Missing auth code');
|
||||
}
|
||||
if (!command.state) {
|
||||
throw new Error('Missing auth state');
|
||||
}
|
||||
|
||||
const user: AuthenticatedUserDTO = {
|
||||
id: 'demo-user',
|
||||
displayName: 'GridPilot Demo Driver',
|
||||
iracingCustomerId: '000000',
|
||||
primaryDriverId: this.seedDriverId,
|
||||
avatarUrl: `/api/avatar/${this.seedDriverId}`,
|
||||
};
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { cookies } from 'next/headers';
|
||||
import { randomUUID } from 'crypto';
|
||||
import type { AuthenticatedUserDTO } from '../../application/dto/AuthenticatedUserDTO';
|
||||
import type { AuthSessionDTO } from '../../application/dto/AuthSessionDTO';
|
||||
import type { IdentitySessionPort } from '../../application/ports/IdentitySessionPort';
|
||||
|
||||
const SESSION_COOKIE = 'gp_demo_session';
|
||||
|
||||
function parseCookieValue(raw: string | undefined): AuthSessionDTO | null {
|
||||
if (!raw) return null;
|
||||
try {
|
||||
const parsed = JSON.parse(raw) as AuthSessionDTO;
|
||||
if (!parsed.expiresAt || Date.now() > parsed.expiresAt) {
|
||||
return null;
|
||||
}
|
||||
return parsed;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function serializeSession(session: AuthSessionDTO): string {
|
||||
return JSON.stringify(session);
|
||||
}
|
||||
|
||||
export class CookieIdentitySessionAdapter implements IdentitySessionPort {
|
||||
async getCurrentSession(): Promise<AuthSessionDTO | null> {
|
||||
const store = await cookies();
|
||||
const raw = store.get(SESSION_COOKIE)?.value;
|
||||
return parseCookieValue(raw);
|
||||
}
|
||||
|
||||
async createSession(user: AuthenticatedUserDTO): Promise<AuthSessionDTO> {
|
||||
const now = Date.now();
|
||||
const expiresAt = now + 24 * 60 * 60 * 1000;
|
||||
|
||||
const session: AuthSessionDTO = {
|
||||
user,
|
||||
issuedAt: now,
|
||||
expiresAt,
|
||||
token: randomUUID(),
|
||||
};
|
||||
|
||||
const store = await cookies();
|
||||
store.set(SESSION_COOKIE, serializeSession(session), {
|
||||
httpOnly: true,
|
||||
sameSite: 'lax',
|
||||
path: '/',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
});
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
async clearSession(): Promise<void> {
|
||||
const store = await cookies();
|
||||
store.delete(SESSION_COOKIE);
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
"types": "./index.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./domain/*": "./domain/*"
|
||||
"./domain/*": "./domain/*",
|
||||
"./application/*": "./application/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"zod": "^3.25.76"
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"declaration": true,
|
||||
"declarationMap": false
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"packages/*": ["packages/*"],
|
||||
"apps/*": ["apps/*"],
|
||||
"@gridpilot/shared-result": ["packages/shared/result/Result.ts"],
|
||||
"@gridpilot/automation/*": ["packages/automation/*"]
|
||||
"@gridpilot/automation/*": ["packages/automation/*"],
|
||||
"@gridpilot/testing-support": ["packages/testing-support/index.ts"]
|
||||
},
|
||||
"types": ["vitest/globals", "node"],
|
||||
"jsx": "react-jsx"
|
||||
|
||||
Reference in New Issue
Block a user