|
1 | 1 | import { describe, expect, it } from 'vitest'; |
2 | 2 |
|
3 | | -import { createCheckAuthorization, splitByScope } from '../authorization'; |
| 3 | +import { createCheckAuthorization, createCheckAuthorizationFromOAuthScopes, splitByScope } from '../authorization'; |
4 | 4 |
|
5 | 5 | describe('createCheckAuthorization', () => { |
6 | 6 | it('correctly parses features', () => { |
@@ -377,6 +377,91 @@ describe('createCheckAuthorization', () => { |
377 | 377 | }); |
378 | 378 | }); |
379 | 379 |
|
| 380 | +describe('createCheckAuthorizationFromOAuthScopes', () => { |
| 381 | + it('authorizes an exact, case-sensitive scope match', () => { |
| 382 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 383 | + userId: 'user_123', |
| 384 | + oauthScopes: ['profile', 'Email'], |
| 385 | + }); |
| 386 | + |
| 387 | + expect(has({ oauth_scope: 'profile' })).toBe(true); |
| 388 | + expect(has({ oauth_scope: 'Email' })).toBe(true); |
| 389 | + expect(has({ oauth_scope: 'PROFILE' })).toBe(false); |
| 390 | + expect(has({ oauth_scope: 'email' })).toBe(false); |
| 391 | + expect(has({ oauth_scope: 'missing' })).toBe(false); |
| 392 | + }); |
| 393 | + |
| 394 | + it('fails closed for invalid requested scopes', () => { |
| 395 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 396 | + userId: 'user_123', |
| 397 | + oauthScopes: ['profile'], |
| 398 | + }); |
| 399 | + |
| 400 | + expect(has({ oauth_scope: '' })).toBe(false); |
| 401 | + expect(has({ oauth_scope: null } as any)).toBe(false); |
| 402 | + expect(has({ oauth_scope: 123 } as any)).toBe(false); |
| 403 | + expect(has({ oauth_scope: undefined } as any)).toBe(false); |
| 404 | + }); |
| 405 | + |
| 406 | + it.each([undefined, null, {}, ['profile', 123], ['profile', '']])( |
| 407 | + 'fails closed for malformed granted scopes: %j', |
| 408 | + oauthScopes => { |
| 409 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 410 | + userId: 'user_123', |
| 411 | + oauthScopes: oauthScopes as any, |
| 412 | + }); |
| 413 | + |
| 414 | + expect(has({ oauth_scope: 'profile' })).toBe(false); |
| 415 | + }, |
| 416 | + ); |
| 417 | + |
| 418 | + it.each([undefined, null, ''])('fails when userId is missing: %j', userId => { |
| 419 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 420 | + userId, |
| 421 | + oauthScopes: ['profile'], |
| 422 | + }); |
| 423 | + |
| 424 | + expect(has({ oauth_scope: 'profile' })).toBe(false); |
| 425 | + }); |
| 426 | + |
| 427 | + it('accepts previous session authorization shapes but denies them', () => { |
| 428 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 429 | + userId: 'user_123', |
| 430 | + oauthScopes: ['profile'], |
| 431 | + }); |
| 432 | + |
| 433 | + expect(has({ role: 'org:admin' })).toBe(false); |
| 434 | + expect(has({ permission: 'org:read' })).toBe(false); |
| 435 | + expect(has({ feature: 'user:premium' })).toBe(false); |
| 436 | + expect(has({ plan: 'user:pro' })).toBe(false); |
| 437 | + expect(has({ reverification: 'strict' })).toBe(false); |
| 438 | + }); |
| 439 | + |
| 440 | + it.each([ |
| 441 | + { role: 'org:admin' }, |
| 442 | + { permission: 'org:read' }, |
| 443 | + { feature: 'user:premium' }, |
| 444 | + { plan: 'user:pro' }, |
| 445 | + { reverification: 'strict' }, |
| 446 | + ])('denies a valid OAuth scope mixed with another recognized dimension: %j', params => { |
| 447 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 448 | + userId: 'user_123', |
| 449 | + oauthScopes: ['profile'], |
| 450 | + }); |
| 451 | + |
| 452 | + expect(has({ oauth_scope: 'profile', ...params } as any)).toBe(false); |
| 453 | + }); |
| 454 | + |
| 455 | + it('fails when no authorization dimension is requested', () => { |
| 456 | + const has = createCheckAuthorizationFromOAuthScopes({ |
| 457 | + userId: 'user_123', |
| 458 | + oauthScopes: ['profile'], |
| 459 | + }); |
| 460 | + |
| 461 | + expect(has({})).toBe(false); |
| 462 | + }); |
| 463 | +}); |
| 464 | + |
380 | 465 | describe('splitByScope', () => { |
381 | 466 | it('correctly splits features by scope', () => { |
382 | 467 | const { org, user } = splitByScope('o:reservations,u:dashboard'); |
|
0 commit comments