Initialize Nuxt.js project with Docker deployment setup

- Add core Nuxt.js application structure with TypeScript
- Include Docker configuration and deployment guide
- Set up project scaffolding with pages, composables, and middleware
- Add environment configuration and Git ignore rules
This commit is contained in:
2025-08-06 14:31:16 +02:00
parent 4ccccde3e4
commit 024d0da617
26 changed files with 1860 additions and 0 deletions

83
server/utils/keycloak.ts Normal file
View File

@@ -0,0 +1,83 @@
import type { KeycloakConfig, TokenResponse, UserInfo } from '~/utils/types';
export class KeycloakClient {
private config: KeycloakConfig;
constructor(config: KeycloakConfig) {
this.config = config;
}
getAuthUrl(state: string): string {
const params = new URLSearchParams({
client_id: this.config.clientId,
redirect_uri: this.config.callbackUrl,
response_type: 'code',
scope: 'openid email profile',
state,
});
return `${this.config.issuer}/protocol/openid-connect/auth?${params}`;
}
async exchangeCodeForTokens(code: string): Promise<TokenResponse> {
const response = await fetch(`${this.config.issuer}/protocol/openid-connect/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
code,
redirect_uri: this.config.callbackUrl,
}),
});
if (!response.ok) {
throw new Error(`Token exchange failed: ${response.statusText}`);
}
return response.json();
}
async getUserInfo(accessToken: string): Promise<UserInfo> {
const response = await fetch(`${this.config.issuer}/protocol/openid-connect/userinfo`, {
headers: {
Authorization: `Bearer ${accessToken}`,
},
});
if (!response.ok) {
throw new Error(`Failed to get user info: ${response.statusText}`);
}
return response.json();
}
async refreshToken(refreshToken: string): Promise<TokenResponse> {
const response = await fetch(`${this.config.issuer}/protocol/openid-connect/token`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
grant_type: 'refresh_token',
client_id: this.config.clientId,
client_secret: this.config.clientSecret,
refresh_token: refreshToken,
}),
});
if (!response.ok) {
throw new Error(`Token refresh failed: ${response.statusText}`);
}
return response.json();
}
}
export function createKeycloakClient(): KeycloakClient {
const config = useRuntimeConfig();
return new KeycloakClient(config.keycloak);
}

81
server/utils/session.ts Normal file
View File

@@ -0,0 +1,81 @@
import { serialize, parse } from 'cookie';
import { createCipheriv, createDecipheriv, randomBytes } from 'crypto';
import type { SessionData } from '~/utils/types';
export class SessionManager {
private encryptionKey: Buffer;
private cookieName = 'monacousa-session';
constructor(encryptionKey: string) {
this.encryptionKey = Buffer.from(encryptionKey, 'hex');
}
private encrypt(data: string): string {
const iv = randomBytes(16);
const cipher = createCipheriv('aes-256-cbc', this.encryptionKey, iv);
let encrypted = cipher.update(data, 'utf8', 'hex');
encrypted += cipher.final('hex');
return iv.toString('hex') + ':' + encrypted;
}
private decrypt(encryptedData: string): string {
const [ivHex, encrypted] = encryptedData.split(':');
const iv = Buffer.from(ivHex, 'hex');
const decipher = createDecipheriv('aes-256-cbc', this.encryptionKey, iv);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
createSession(sessionData: SessionData): string {
const data = JSON.stringify(sessionData);
const encrypted = this.encrypt(data);
return serialize(this.cookieName, encrypted, {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 60 * 60 * 24 * 7, // 7 days
path: '/',
});
}
getSession(cookieHeader?: string): SessionData | null {
if (!cookieHeader) return null;
const cookies = parse(cookieHeader);
const sessionCookie = cookies[this.cookieName];
if (!sessionCookie) return null;
try {
const decrypted = this.decrypt(sessionCookie);
const sessionData = JSON.parse(decrypted) as SessionData;
// Check if session is expired
if (Date.now() > sessionData.tokens.expiresAt) {
return null;
}
return sessionData;
} catch (error) {
console.error('Failed to decrypt session:', error);
return null;
}
}
destroySession(): string {
return serialize(this.cookieName, '', {
httpOnly: true,
secure: true,
sameSite: 'lax',
maxAge: 0,
path: '/',
});
}
}
export function createSessionManager(): SessionManager {
const config = useRuntimeConfig();
return new SessionManager(config.encryptionKey);
}