Skip to content

Commit cb5fce8

Browse files
committed
Create Identity.asRole
This moves all session creations into the service.
1 parent 415e2c4 commit cb5fce8

File tree

5 files changed

+68
-69
lines changed

5 files changed

+68
-69
lines changed

src/components/authentication/authentication.service.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,17 @@ export class AuthenticationService {
256256
return session;
257257
}
258258

259+
asRole<R>(role: Role, fn: () => R): R {
260+
const session: Session = {
261+
token: 'system',
262+
issuedAt: DateTime.now(),
263+
userId: 'anonymous' as ID,
264+
anonymous: false,
265+
roles: [`global:${role}`],
266+
};
267+
return this.sessionHost.withSession(session, fn);
268+
}
269+
259270
async changePassword(
260271
oldPassword: string,
261272
newPassword: string,

src/components/authorization/policy/executor/policy-dumper.ts

Lines changed: 42 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,13 @@ import { Chalk, type ChalkInstance } from 'chalk';
1515
import Table from 'cli-table3';
1616
import { Command, Option } from 'clipanion';
1717
import { startCase } from 'lodash';
18-
import { DateTime } from 'luxon';
1918
import fs from 'node:fs/promises';
2019
import { type LiteralUnion } from 'type-fest';
2120
import { inspect } from 'util';
2221
import xlsx from 'xlsx';
23-
import {
24-
type EnhancedResource,
25-
firstOr,
26-
type ID,
27-
Role,
28-
type Session,
29-
} from '~/common';
22+
import { type EnhancedResource, firstOr, Role } from '~/common';
3023
import { searchCamelCase } from '~/common/search-camel-case';
24+
import { Identity } from '~/core/authentication';
3125
import { InjectableCommand } from '~/core/cli';
3226
import { type ResourceLike, ResourcesHost } from '~/core/resources';
3327
import {
@@ -45,6 +39,7 @@ type AnyResource = EnhancedResource<any>;
4539
@Injectable()
4640
export class PolicyDumper {
4741
constructor(
42+
private readonly identity: Identity,
4843
private readonly resources: ResourcesHost,
4944
private readonly executor: PolicyExecutor,
5045
) {}
@@ -173,51 +168,45 @@ export class PolicyDumper {
173168
resource: AnyResource,
174169
options: { props: boolean | ReadonlySet<string> },
175170
): DumpedRow[] {
176-
const session: Session = {
177-
token: 'system',
178-
issuedAt: DateTime.now(),
179-
userId: 'anonymous' as ID,
180-
anonymous: false,
181-
roles: [`global:${role}`],
182-
};
183-
const resolve = (action: string, prop?: string) =>
184-
this.executor.resolve({
185-
session,
186-
resource,
187-
calculatedAsCondition: true,
188-
optimizeConditions: true,
189-
action,
190-
prop,
191-
});
192-
return [
193-
{
194-
role,
195-
resource,
196-
edge: undefined,
197-
...mapValues.fromList(ResourceAction, (action) => resolve(action))
198-
.asRecord,
199-
},
200-
...(options.props !== false
201-
? ([
202-
[resource.securedPropsPlusExtra, PropAction],
203-
[resource.childSingleKeys, ChildSingleAction],
204-
[resource.childListKeys, ChildListAction],
205-
] as const)
206-
: []
207-
).flatMap(([set, actions]) =>
208-
[...set]
209-
.filter(
210-
(p) => typeof options.props === 'boolean' || options.props.has(p),
211-
)
212-
.map((prop) => ({
213-
role,
214-
resource,
215-
edge: prop,
216-
...mapValues.fromList(actions, (action) => resolve(action, prop))
217-
.asRecord,
218-
})),
219-
),
220-
];
171+
return this.identity.asRole(role, () => {
172+
const resolve = (action: string, prop?: string) =>
173+
this.executor.resolve({
174+
resource,
175+
calculatedAsCondition: true,
176+
optimizeConditions: true,
177+
action,
178+
prop,
179+
});
180+
return [
181+
{
182+
role,
183+
resource,
184+
edge: undefined,
185+
...mapValues.fromList(ResourceAction, (action) => resolve(action))
186+
.asRecord,
187+
},
188+
...(options.props !== false
189+
? ([
190+
[resource.securedPropsPlusExtra, PropAction],
191+
[resource.childSingleKeys, ChildSingleAction],
192+
[resource.childListKeys, ChildListAction],
193+
] as const)
194+
: []
195+
).flatMap(([set, actions]) =>
196+
[...set]
197+
.filter(
198+
(p) => typeof options.props === 'boolean' || options.props.has(p),
199+
)
200+
.map((prop) => ({
201+
role,
202+
resource,
203+
edge: prop,
204+
...mapValues.fromList(actions, (action) => resolve(action, prop))
205+
.asRecord,
206+
})),
207+
),
208+
];
209+
});
221210
}
222211
}
223212

src/components/workflow/permission.serializer.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { setOf } from '@seedcompany/common';
2-
import { DateTime } from 'luxon';
3-
import { type ID, Role, type Session } from '~/common';
4-
import { type SessionHost } from '../authentication';
2+
import { type ID, Role } from '~/common';
3+
import { type Identity } from '~/core/authentication';
54
import { type Privileges, type UserResourcePrivileges } from '../authorization';
65
import { Condition } from '../authorization/policy/conditions';
76
import { type Workflow } from './define-workflow';
@@ -12,18 +11,11 @@ export const transitionPermissionSerializer =
1211
<W extends Workflow>(
1312
workflow: W,
1413
privileges: Privileges,
15-
sessionHost: SessionHost,
14+
identity: Identity,
1615
) =>
1716
(transition: W['transition']): readonly SerializedTransitionPermission[] => {
1817
const all = [...Role].flatMap((role) => {
19-
const session: Session = {
20-
token: 'system',
21-
issuedAt: DateTime.now(),
22-
userId: 'anonymous' as ID,
23-
anonymous: false,
24-
roles: [`global:${role}`],
25-
};
26-
return sessionHost.withSession(session, () => {
18+
return identity.asRole(role, () => {
2719
const p = privileges.for(workflow.eventResource);
2820
const readEvent = resolve(p, 'read', transition.key);
2921
const execute = resolve(p, 'create', transition.key);

src/components/workflow/workflow.service.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { Inject, Injectable } from '@nestjs/common';
22
import { type Nil } from '@seedcompany/common';
33
import { type ID, UnauthorizedException } from '~/common';
4-
import { SessionHost } from '../authentication';
4+
import { Identity } from '~/core/authentication';
55
import { Privileges } from '../authorization';
66
import { MissingContextException } from '../authorization/policy/conditions';
77
import { type Workflow } from './define-workflow';
@@ -20,7 +20,7 @@ export const WorkflowService = <W extends Workflow>(workflow: () => W) => {
2020
@Injectable()
2121
abstract class WorkflowServiceClass {
2222
@Inject() protected readonly privileges: Privileges;
23-
@Inject() protected readonly sessionHost: SessionHost;
23+
@Inject() protected readonly identity: Identity;
2424
protected readonly workflow: W;
2525

2626
constructor() {
@@ -143,7 +143,7 @@ export const WorkflowService = <W extends Workflow>(workflow: () => W) => {
143143
transitionPermissionSerializer(
144144
this.workflow,
145145
this.privileges,
146-
this.sessionHost,
146+
this.identity,
147147
),
148148
);
149149
}

src/core/authentication/identity.service.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Inject, Injectable } from '@nestjs/common';
2-
import type { ID } from '~/common';
2+
import type { ID, Role } from '~/common';
33
import { type AuthenticationService } from '../../components/authentication';
44
import { SessionHost } from '../../components/authentication/session.host';
55

@@ -29,4 +29,11 @@ export class Identity {
2929
async asUser<R>(user: ID<'User'>, fn: () => Promise<R>): Promise<R> {
3030
return await this.auth.asUser(user, fn);
3131
}
32+
33+
/**
34+
* Run this function with the current user as an ephemeral one this role
35+
*/
36+
asRole<R>(role: Role, fn: () => R): R {
37+
return this.auth.asRole(role, fn);
38+
}
3239
}

0 commit comments

Comments
 (0)