feat(identity): redesign identity module and introduce RBAC foundation
- Redesign the Identity module with a richer domain model. - Extend the User entity to support username, Authentik integration, activity tracking, and storage information. - Add Role and Permission domain models with many-to-many relationships. - Implement RBAC foundation using UserRole, RolePermission, and UserPermission mappings. - Add user storage quota and usage fields with default values. - Introduce Authentik identifiers and synchronization metadata. - Refactor user domain logic for role and permission management. - Update Prisma schema to support the new identity architecture. - Improve JWT authentication and permission guard integration. - Update repositories, handlers, controllers, mappers, DTOs, and Swagger configuration. - Refresh environment configuration and project dependencies.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
import { chromium } from 'playwright';
|
||||
import { env } from './env';
|
||||
|
||||
const store: Map<string, { token: string; expiresAt?: number }> = new Map();
|
||||
|
||||
function setToken(key: string, token: string, expiresIn?: number) {
|
||||
const expiresAt = expiresIn ? Date.now() + expiresIn * 1000 - 5000 : undefined;
|
||||
store.set(key, { token, expiresAt });
|
||||
}
|
||||
|
||||
function getTokenFromStore(key: string) {
|
||||
const entry = store.get(key);
|
||||
if (!entry) return null;
|
||||
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
||||
store.delete(key);
|
||||
return null;
|
||||
}
|
||||
return entry.token;
|
||||
}
|
||||
|
||||
async function loginViaRaylab(username: string, password: string) {
|
||||
const base = env.BASE_URL || 'http://localhost:3000';
|
||||
const browser = await chromium.launch({ headless: Boolean(process.env.PW_HEADLESS || '1') });
|
||||
const context = await browser.newContext();
|
||||
const page = await context.newPage();
|
||||
|
||||
// Navigate to RayLab - expects redirect to identity provider
|
||||
await page.goto(base, { waitUntil: 'networkidle' });
|
||||
|
||||
// Wait for a login form to appear on identity provider
|
||||
try {
|
||||
await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 });
|
||||
// fill username
|
||||
const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]');
|
||||
if (usernameInput) await usernameInput.fill(username);
|
||||
|
||||
// fill password
|
||||
const passwordInput = await page.$('input[type="password"], input[name="password"]');
|
||||
if (passwordInput) await passwordInput.fill(password);
|
||||
|
||||
// try to submit
|
||||
const submitButton = await page.$('button[type="submit"], input[type="submit"]');
|
||||
if (submitButton) {
|
||||
await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]);
|
||||
}
|
||||
} catch (e) {
|
||||
// If no login form found, we may already be at callback
|
||||
}
|
||||
|
||||
// Wait for callback to be done and cookie to be set
|
||||
await page.waitForTimeout(1000);
|
||||
const cookies = await context.cookies();
|
||||
const jwtCookie = cookies.find(c => c.name === 'raylab_jwt');
|
||||
const refreshCookie = cookies.find(c => c.name === 'raylab_refresh');
|
||||
|
||||
const result: any = {};
|
||||
if (jwtCookie) result.accessToken = jwtCookie.value;
|
||||
if (refreshCookie) result.refreshToken = refreshCookie.value;
|
||||
|
||||
await context.close();
|
||||
await browser.close();
|
||||
|
||||
if (!result.accessToken) throw new Error('Login failed: no session cookie set');
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export async function getToken(role: 'owner' | 'admin' | 'employee' = 'admin') {
|
||||
const cacheKey = `role:${role}`;
|
||||
const cached = getTokenFromStore(cacheKey);
|
||||
if (cached) return cached;
|
||||
|
||||
const username = role === 'owner' ? env.OWNER_USERNAME : role === 'admin' ? env.ADMIN_USERNAME : env.EMPLOYEE_USERNAME;
|
||||
const password = role === 'owner' ? env.OWNER_PASSWORD : role === 'admin' ? env.ADMIN_PASSWORD : env.EMPLOYEE_PASSWORD;
|
||||
|
||||
if (!username || !password) throw new Error('Missing credentials in .env for role: ' + role);
|
||||
|
||||
const data = await loginViaRaylab(username, password);
|
||||
const token = data.accessToken;
|
||||
// expiresIn not available from cookie - default to 1 hour
|
||||
const expiresIn = Number(process.env.TEST_TOKEN_EXPIRES_IN || 3600);
|
||||
setToken(cacheKey, token, expiresIn);
|
||||
return token;
|
||||
}
|
||||
|
||||
export async function getRawToken(username: string, password: string) {
|
||||
const data = await loginViaRaylab(username, password);
|
||||
return data.accessToken;
|
||||
}
|
||||
|
||||
export default { getToken, getRawToken };
|
||||
Reference in New Issue
Block a user