Files
Rayyan 7ce0de4e91 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.
2026-08-02 00:25:36 +07:00

136 lines
6.6 KiB
Markdown

# Instructions
- Following Playwright test failed.
- Explain why, be concise, respect Playwright best practices.
- Provide a snippet of code with the fix, if possible.
# Test info
- Name: user.integration.spec.ts >> Integration - User lifecycle >> login via Authentik (created user) issues internal JWT
- Location: tests\integration\user.integration.spec.ts:140:7
# Error details
```
Error: No jwt cookie set after login (second test). Saved screenshot/cookies/page to tests/test-artifacts
```
# Test source
```ts
76 |
77 | test.beforeAll(async () => {
78 | // Integration tests assume test users are created in Authentik prior to running.
79 | // RayLab will create local user record upon first successful login via Authentik.
80 | });
81 |
82 | test('login via Authentik creates local user and issues internal JWT', async () => {
83 | // use browser flow to login as the test user
84 | const browser = await chromium.launch({ headless: true });
85 | const context = await browser.newContext();
86 | const page = await context.newPage();
87 | await page.goto(BASE_URL, { waitUntil: 'networkidle' });
88 |
89 | try {
90 | await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 });
91 | const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]');
92 | if (usernameInput) await usernameInput.fill(TEST_USER_USERNAME);
93 | const passwordInput = await page.$('input[type="password"], input[name="password"]');
94 | if (passwordInput) await passwordInput.fill(TEST_USER_PASSWORD);
95 | const submitButton = await page.$('button[type="submit"], input[type="submit"]');
96 | if (submitButton) {
97 | await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]);
98 | }
99 | } catch (e) {
100 | // ignore if login form not present
101 | }
102 |
103 | await page.waitForTimeout(1000);
104 | const cookies = await context.cookies();
105 | const jwtCookie = cookies.find(c => c.name === 'raylab_jwt');
106 | const refreshCookie = cookies.find(c => c.name === 'raylab_refresh');
107 |
108 | if (!jwtCookie) {
109 | const fs = require('fs');
110 | const dir = 'tests/test-artifacts';
111 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
112 | const ts = Date.now();
113 | await page.screenshot({ path: `${dir}/failed-login-${ts}.png`, fullPage: true });
114 | fs.writeFileSync(`${dir}/failed-login-${ts}-cookies.json`, JSON.stringify(cookies, null, 2), 'utf-8');
115 | const content = await page.content();
116 | fs.writeFileSync(`${dir}/failed-login-${ts}-page.html`, content, 'utf-8');
117 | await context.close();
118 | await browser.close();
119 | throw new Error(`No jwt cookie set after login. Saved screenshot/cookies/page to ${dir}`);
120 | }
121 |
122 | // ensure we have an internal jwt cookie
123 | expect(jwtCookie).toBeDefined();
124 | expect(jwtCookie.value).toBeTruthy();
125 |
126 | // validate /auth/me using the internal jwt
127 | const token = jwtCookie.value;
128 | const meUrl = new URL('/auth/me', BASE_URL).toString();
129 | const meResp = await fetch(meUrl, { headers: { Authorization: `Bearer ${token}` } });
130 | const meJson = await meResp.json();
131 | expect(meResp.status).toBeLessThan(300);
132 | expect(meJson.success).toBe(true);
133 | expect(meJson.data).toBeDefined();
134 | expect(meJson.data.email).toBe(TEST_USER_EMAIL);
135 |
136 | await context.close();
137 | await browser.close();
138 | });
139 |
140 | test('login via Authentik (created user) issues internal JWT', async () => {
141 | // use browser flow to login as the created user
142 | const browser = await chromium.launch({ headless: true });
143 | const context = await browser.newContext();
144 | const page = await context.newPage();
145 | await page.goto(BASE_URL, { waitUntil: 'networkidle' });
146 |
147 | try {
148 | await page.waitForSelector('input[type="text"], input[type="email"], input[name="username"]', { timeout: 10000 });
149 | const usernameInput = await page.$('input[name="username"]') || await page.$('input[type="email"]') || await page.$('input[type="text"]');
150 | if (usernameInput) await usernameInput.fill(TEST_USER_USERNAME);
151 | const passwordInput = await page.$('input[type="password"], input[name="password"]');
152 | if (passwordInput) await passwordInput.fill(TEST_USER_PASSWORD);
153 | const submitButton = await page.$('button[type="submit"], input[type="submit"]');
154 | if (submitButton) {
155 | await Promise.all([page.waitForNavigation({ waitUntil: 'networkidle', timeout: 20000 }), submitButton.click()]);
156 | }
157 | } catch (e) {
158 | // ignore if login form not present
159 | }
160 |
161 | await page.waitForTimeout(1000);
162 | const cookies = await context.cookies();
163 | const jwtCookie = cookies.find(c => c.name === 'raylab_jwt');
164 |
165 | if (!jwtCookie) {
166 | const fs = require('fs');
167 | const dir = 'tests/test-artifacts';
168 | if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
169 | const ts = Date.now();
170 | await page.screenshot({ path: `${dir}/failed-login-2-${ts}.png`, fullPage: true });
171 | fs.writeFileSync(`${dir}/failed-login-2-${ts}-cookies.json`, JSON.stringify(cookies, null, 2), 'utf-8');
172 | const content = await page.content();
173 | fs.writeFileSync(`${dir}/failed-login-2-${ts}-page.html`, content, 'utf-8');
174 | await context.close();
175 | await browser.close();
> 176 | throw new Error(`No jwt cookie set after login (second test). Saved screenshot/cookies/page to ${dir}`);
| ^ Error: No jwt cookie set after login (second test). Saved screenshot/cookies/page to tests/test-artifacts
177 | }
178 |
179 | await context.close();
180 | await browser.close();
181 |
182 | expect(jwtCookie).toBeDefined();
183 | expect(jwtCookie.value).toBeTruthy();
184 | });
185 |
186 | // Cleanup via API provisioning has been removed. If test environment requires cleanup, perform manually in Authentik.
187 |
188 | });
189 |
```