Skip to content

Commit c6badf5

Browse files
authored
chore: cleanup authorization code (#6736)
1 parent d1b8d4c commit c6badf5

19 files changed

+145
-718
lines changed

codegen.mts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,9 @@ const config: CodegenConfig = {
3636
ProjectType: '../shared/entities#ProjectType',
3737
NativeFederationCompatibilityStatus:
3838
'../shared/entities#NativeFederationCompatibilityStatus',
39-
TargetAccessScope: '../modules/auth/providers/target-access#TargetAccessScope',
40-
ProjectAccessScope: '../modules/auth/providers/project-access#ProjectAccessScope',
41-
OrganizationAccessScope:
42-
'../modules/auth/providers/organization-access#OrganizationAccessScope',
39+
TargetAccessScope: '../modules/auth/providers/scopes#TargetAccessScope',
40+
ProjectAccessScope: '../modules/auth/providers/scopes#ProjectAccessScope',
41+
OrganizationAccessScope: '../modules/auth/providers/scopes#OrganizationAccessScope',
4342
SupportTicketPriority: '../shared/entities#SupportTicketPriority',
4443
SupportTicketStatus: '../shared/entities#SupportTicketStatus',
4544
},
Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
import { createModule } from 'graphql-modules';
22
import { AuditLogManager } from '../audit-logs/providers/audit-logs-manager';
33
import { AuthManager } from './providers/auth-manager';
4-
import { OrganizationAccess } from './providers/organization-access';
54
import { OrganizationAccessTokenValidationCache } from './providers/organization-access-token-validation-cache';
6-
import { ProjectAccess } from './providers/project-access';
7-
import { TargetAccess } from './providers/target-access';
85
import { UserManager } from './providers/user-manager';
96
import { resolvers } from './resolvers.generated';
107
import typeDefs from './module.graphql';
@@ -14,13 +11,5 @@ export const authModule = createModule({
1411
dirname: __dirname,
1512
typeDefs,
1613
resolvers,
17-
providers: [
18-
AuthManager,
19-
UserManager,
20-
OrganizationAccess,
21-
ProjectAccess,
22-
TargetAccess,
23-
AuditLogManager,
24-
OrganizationAccessTokenValidationCache,
25-
],
14+
providers: [AuthManager, UserManager, AuditLogManager, OrganizationAccessTokenValidationCache],
2615
});

packages/services/api/src/modules/auth/lib/authz.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ class TestSession extends Session {
1515
): Promise<Array<AuthorizationPolicyStatement>> | Array<AuthorizationPolicyStatement> {
1616
return this.policyStatements;
1717
}
18+
19+
getActor(): Promise<never> {
20+
throw new Error('Not implemented');
21+
}
1822
}
1923

2024
describe('Session.assertPerformAction', () => {

packages/services/api/src/modules/auth/lib/authz.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type { User } from '../../../shared/entities';
55
import { AccessError } from '../../../shared/errors';
66
import { objectEntries, objectFromEntries } from '../../../shared/helpers';
77
import { isUUID } from '../../../shared/is-uuid';
8+
import type { OrganizationAccessToken } from '../../organization/providers/organization-access-tokens';
89
import { Logger } from '../../shared/providers/logger';
910

1011
export type AuthorizationPolicyStatement = {
@@ -47,6 +48,18 @@ function parseResourceIdentifier(resource: string) {
4748
return { organizationId, resourceId: parts[2] };
4849
}
4950

51+
export type UserActor = {
52+
type: 'user';
53+
user: User;
54+
};
55+
56+
export type OrganizationAccessTokenActor = {
57+
type: 'organizationAccessToken';
58+
organizationAccessToken: OrganizationAccessToken;
59+
};
60+
61+
type Actor = UserActor | OrganizationAccessTokenActor;
62+
5063
/**
5164
* Abstract session class that is implemented by various ways to identify a session.
5265
* A session is a way to identify a user and their permissions for a specific organization.
@@ -75,8 +88,21 @@ export abstract class Session {
7588
abstract readonly id: string;
7689

7790
/** Retrieve the current viewer. Implementations of the session need to implement this function */
78-
public getViewer(): Promise<User> {
79-
throw new AccessError('Authorization token is missing', 'UNAUTHENTICATED');
91+
public abstract getActor(): Promise<Actor>;
92+
93+
/**
94+
* Retrieve the Viewer of the session.
95+
* A viewer can only be a {User} aka {SuperTokensSessions{}.
96+
* If the session does not have a user an exception is raised.
97+
*/
98+
public async getViewer(): Promise<User> {
99+
const actor = await this.getActor();
100+
101+
if (actor.type !== 'user') {
102+
throw new AccessError('Only authenticated users can perform this action.');
103+
}
104+
105+
return actor.user;
80106
}
81107

82108
public isViewer(): boolean {
@@ -498,6 +524,10 @@ class UnauthenticatedSession extends Session {
498524
return [];
499525
}
500526
id = 'noop';
527+
528+
public getActor(): Promise<Actor> {
529+
throw new AccessError('Authorization token is missing', 'UNAUTHENTICATED');
530+
}
501531
}
502532

503533
/**

packages/services/api/src/modules/auth/lib/organization-access-token-strategy.ts

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import * as crypto from 'node:crypto';
22
import { type FastifyReply, type FastifyRequest } from '@hive/service-common';
33
import * as OrganizationAccessKey from '../../organization/lib/organization-access-key';
4+
import type { OrganizationAccessToken } from '../../organization/providers/organization-access-tokens';
45
import { OrganizationAccessTokensCache } from '../../organization/providers/organization-access-tokens-cache';
56
import { Logger } from '../../shared/providers/logger';
67
import { OrganizationAccessTokenValidationCache } from '../providers/organization-access-token-validation-cache';
7-
import { AuthNStrategy, AuthorizationPolicyStatement, Session } from './authz';
8+
import {
9+
AuthNStrategy,
10+
AuthorizationPolicyStatement,
11+
OrganizationAccessTokenActor,
12+
Session,
13+
} from './authz';
814

915
function hashToken(token: string) {
1016
return crypto.createHash('sha256').update(token).digest('hex');
@@ -13,13 +19,15 @@ function hashToken(token: string) {
1319
export class OrganizationAccessTokenSession extends Session {
1420
public readonly organizationId: string;
1521
private policies: Array<AuthorizationPolicyStatement>;
22+
private organizationAccessToken: OrganizationAccessToken;
1623
readonly id: string;
1724

1825
constructor(
1926
args: {
2027
id: string;
2128
organizationId: string;
2229
policies: Array<AuthorizationPolicyStatement>;
30+
organizationAccessToken: OrganizationAccessToken;
2331
},
2432
deps: {
2533
logger: Logger;
@@ -29,6 +37,14 @@ export class OrganizationAccessTokenSession extends Session {
2937
this.id = args.id;
3038
this.organizationId = args.organizationId;
3139
this.policies = args.policies;
40+
this.organizationAccessToken = args.organizationAccessToken;
41+
}
42+
43+
public async getActor(): Promise<OrganizationAccessTokenActor> {
44+
return {
45+
type: 'organizationAccessToken',
46+
organizationAccessToken: this.organizationAccessToken,
47+
};
3248
}
3349

3450
protected loadPolicyStatementsForOrganization(
@@ -112,6 +128,7 @@ export class OrganizationAccessTokenStrategy extends AuthNStrategy<OrganizationA
112128
id: organizationAccessToken.id,
113129
organizationId: organizationAccessToken.organizationId,
114130
policies: organizationAccessToken.authorizationPolicyStatements,
131+
organizationAccessToken,
115132
},
116133
{
117134
logger: args.req.log,

packages/services/api/src/modules/auth/lib/supertokens-strategy.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,12 @@ import SessionNode from 'supertokens-node/recipe/session/index.js';
22
import * as zod from 'zod';
33
import type { FastifyReply, FastifyRequest } from '@hive/service-common';
44
import { captureException } from '@sentry/node';
5-
import type { User } from '../../../shared/entities';
65
import { AccessError, HiveError } from '../../../shared/errors';
76
import { isUUID } from '../../../shared/is-uuid';
87
import { OrganizationMembers } from '../../organization/providers/organization-members';
98
import { Logger } from '../../shared/providers/logger';
109
import type { Storage } from '../../shared/providers/storage';
11-
import { AuthNStrategy, AuthorizationPolicyStatement, Session } from './authz';
10+
import { AuthNStrategy, AuthorizationPolicyStatement, Session, UserActor } from './authz';
1211

1312
export class SuperTokensCookieBasedSession extends Session {
1413
public superTokensUserId: string;
@@ -32,7 +31,7 @@ export class SuperTokensCookieBasedSession extends Session {
3231
protected async loadPolicyStatementsForOrganization(
3332
organizationId: string,
3433
): Promise<Array<AuthorizationPolicyStatement>> {
35-
const user = await this.getViewer();
34+
const { user } = await this.getActor();
3635

3736
this.logger.debug(
3837
'Loading policy statements for organization. (userId=%s, organizationId=%s)',
@@ -97,7 +96,7 @@ export class SuperTokensCookieBasedSession extends Session {
9796
return organizationMembership.assignedRole.authorizationPolicyStatements;
9897
}
9998

100-
public async getViewer(): Promise<User> {
99+
public async getActor(): Promise<UserActor> {
101100
const user = await this.storage.getUserBySuperTokenId({
102101
superTokensUserId: this.superTokensUserId,
103102
});
@@ -106,7 +105,10 @@ export class SuperTokensCookieBasedSession extends Session {
106105
throw new AccessError('User not found');
107106
}
108107

109-
return user;
108+
return {
109+
type: 'user',
110+
user,
111+
};
110112
}
111113

112114
public isViewer() {

packages/services/api/src/modules/auth/lib/target-access-token-strategy.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { maskToken, type FastifyReply, type FastifyRequest } from '@hive/service-common';
2+
import { AccessError } from '../../../shared/errors';
23
import { Logger } from '../../shared/providers/logger';
34
import { TokenStorage } from '../../token/providers/token-storage';
45
import { TokensConfig } from '../../token/providers/tokens';
@@ -55,6 +56,10 @@ export class TargetAccessTokenSession extends Session {
5556
targetId: this.targetId,
5657
};
5758
}
59+
60+
public async getActor(): Promise<never> {
61+
throw new AccessError('Authorization token is missing', 'UNAUTHENTICATED');
62+
}
5863
}
5964

6065
export class TargetAccessTokenStrategy extends AuthNStrategy<TargetAccessTokenSession> {

packages/services/api/src/modules/auth/module.graphql.mappers.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import type { User } from '../../shared/entities';
22
import { PermissionGroup, PermissionRecord } from '../organization/lib/permissions';
3-
import type { OrganizationAccessScope } from './providers/organization-access';
4-
import type { ProjectAccessScope } from './providers/project-access';
5-
import type { TargetAccessScope } from './providers/target-access';
3+
import type {
4+
OrganizationAccessScope,
5+
ProjectAccessScope,
6+
TargetAccessScope,
7+
} from './providers/scopes';
68

79
export type OrganizationAccessScopeMapper = OrganizationAccessScope;
810
export type ProjectAccessScopeMapper = ProjectAccessScope;

0 commit comments

Comments
 (0)