|
2 | 2 | * @vitest-environment node |
3 | 3 | */ |
4 | 4 |
|
| 5 | +import { serializePrincipal } from '@sim/auth/principal' |
5 | 6 | import { resetEnvMock } from '@sim/testing' |
6 | | -import { decodeJwt } from 'jose' |
| 7 | +import { decodeJwt, SignJWT } from 'jose' |
7 | 8 | import { afterAll, describe, expect, it, vi } from 'vitest' |
| 9 | +import { env } from '@/lib/core/config/env' |
8 | 10 |
|
9 | 11 | vi.unmock('@/lib/auth/internal') |
10 | 12 |
|
@@ -181,7 +183,32 @@ describe('internal executor delegation claims', () => { |
181 | 183 | ).rejects.toBeInstanceOf(InvalidInternalDelegationTokenError) |
182 | 184 | }) |
183 | 185 |
|
184 | | - it('rejects laundering actorless or external principals into a Sim user subject', async () => { |
| 186 | + it('round-trips an authenticated chat subject without inventing a Sim user', async () => { |
| 187 | + const token = await generateInternalDelegationToken({ |
| 188 | + workflowId: 'workflow-1', |
| 189 | + principal: { |
| 190 | + kind: 'system', |
| 191 | + serviceId: 'chat', |
| 192 | + workspaceId: 'workspace-1', |
| 193 | + workflowId: 'workflow-1', |
| 194 | + subject: { kind: 'authenticated_email', email: 'person@example.com' }, |
| 195 | + }, |
| 196 | + }) |
| 197 | + |
| 198 | + await expect(verifyInternalDelegationToken(token)).resolves.toMatchObject({ |
| 199 | + workflowId: 'workflow-1', |
| 200 | + principal: { |
| 201 | + kind: 'system', |
| 202 | + serviceId: 'chat', |
| 203 | + workspaceId: 'workspace-1', |
| 204 | + workflowId: 'workflow-1', |
| 205 | + subject: { kind: 'authenticated_email', email: 'person@example.com' }, |
| 206 | + }, |
| 207 | + }) |
| 208 | + expect(decodeJwt(token).sub).toBeUndefined() |
| 209 | + }) |
| 210 | + |
| 211 | + it('rejects laundering actorless or non-Sim principals into a Sim user subject', async () => { |
185 | 212 | await expect( |
186 | 213 | generateInternalDelegationToken({ |
187 | 214 | subjectUserId: 'billing-owner', |
@@ -213,7 +240,49 @@ describe('internal executor delegation claims', () => { |
213 | 240 | }, |
214 | 241 | }, |
215 | 242 | }) |
216 | | - ).rejects.toThrow('External workflow subjects cannot be represented as Sim users') |
| 243 | + ).rejects.toThrow('Non-Sim workflow subjects cannot be represented as Sim users') |
| 244 | + |
| 245 | + await expect( |
| 246 | + generateInternalDelegationToken({ |
| 247 | + subjectUserId: 'unrelated-user', |
| 248 | + workflowId: 'workflow-1', |
| 249 | + principal: { |
| 250 | + kind: 'system', |
| 251 | + serviceId: 'chat', |
| 252 | + workspaceId: 'workspace-1', |
| 253 | + workflowId: 'workflow-1', |
| 254 | + subject: { kind: 'authenticated_email', email: 'person@example.com' }, |
| 255 | + }, |
| 256 | + }) |
| 257 | + ).rejects.toThrow('Non-Sim workflow subjects cannot be represented as Sim users') |
| 258 | + }) |
| 259 | + |
| 260 | + it('rejects a signed delegation that pairs a non-Sim principal with a Sim user subject', async () => { |
| 261 | + const issuedAt = Math.floor(Date.now() / 1000) |
| 262 | + const token = await new SignJWT({ |
| 263 | + type: 'internal_delegation', |
| 264 | + serviceId: 'executor', |
| 265 | + workflowId: 'workflow-1', |
| 266 | + principal: serializePrincipal({ |
| 267 | + kind: 'system', |
| 268 | + serviceId: 'chat', |
| 269 | + workspaceId: 'workspace-1', |
| 270 | + workflowId: 'workflow-1', |
| 271 | + subject: { kind: 'authenticated_email', email: 'person@example.com' }, |
| 272 | + }), |
| 273 | + }) |
| 274 | + .setProtectedHeader({ alg: 'HS256' }) |
| 275 | + .setJti('delegation-1') |
| 276 | + .setSubject('unrelated-user') |
| 277 | + .setIssuedAt(issuedAt) |
| 278 | + .setExpirationTime(issuedAt + 5 * 60) |
| 279 | + .setIssuer('sim-internal') |
| 280 | + .setAudience('sim-api') |
| 281 | + .sign(new TextEncoder().encode(env.INTERNAL_API_SECRET)) |
| 282 | + |
| 283 | + await expect(verifyInternalDelegationToken(token)).rejects.toBeInstanceOf( |
| 284 | + InvalidInternalDelegationTokenError |
| 285 | + ) |
217 | 286 | }) |
218 | 287 |
|
219 | 288 | it('derives issued-at and expiry from one timestamp', async () => { |
|
0 commit comments