@tekki-forge/runtime-api 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -0
- package/dist/access.d.ts +15 -0
- package/dist/access.js +51 -0
- package/dist/access.js.map +1 -0
- package/dist/api-keys.d.ts +140 -0
- package/dist/api-keys.js +361 -0
- package/dist/api-keys.js.map +1 -0
- package/dist/backup.d.ts +142 -0
- package/dist/backup.js +280 -0
- package/dist/backup.js.map +1 -0
- package/dist/bootstrap.d.ts +57 -0
- package/dist/bootstrap.js +191 -0
- package/dist/bootstrap.js.map +1 -0
- package/dist/comments.d.ts +139 -0
- package/dist/comments.js +370 -0
- package/dist/comments.js.map +1 -0
- package/dist/computed.d.ts +27 -0
- package/dist/computed.js +174 -0
- package/dist/computed.js.map +1 -0
- package/dist/controllers.d.ts +309 -0
- package/dist/controllers.js +634 -0
- package/dist/controllers.js.map +1 -0
- package/dist/dashboard.d.ts +58 -0
- package/dist/dashboard.js +258 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/errors.d.ts +6 -0
- package/dist/errors.js +55 -0
- package/dist/errors.js.map +1 -0
- package/dist/events.d.ts +39 -0
- package/dist/events.js +36 -0
- package/dist/events.js.map +1 -0
- package/dist/extension.d.ts +103 -0
- package/dist/extension.js +8 -0
- package/dist/extension.js.map +1 -0
- package/dist/import.d.ts +69 -0
- package/dist/import.js +347 -0
- package/dist/import.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/jobs.d.ts +41 -0
- package/dist/jobs.js +10 -0
- package/dist/jobs.js.map +1 -0
- package/dist/notifications.d.ts +61 -0
- package/dist/notifications.js +195 -0
- package/dist/notifications.js.map +1 -0
- package/dist/plugin.d.ts +43 -0
- package/dist/plugin.js +4 -0
- package/dist/plugin.js.map +1 -0
- package/dist/query.d.ts +29 -0
- package/dist/query.js +192 -0
- package/dist/query.js.map +1 -0
- package/dist/saved-views.d.ts +42 -0
- package/dist/saved-views.js +130 -0
- package/dist/saved-views.js.map +1 -0
- package/dist/search.d.ts +17 -0
- package/dist/search.js +42 -0
- package/dist/search.js.map +1 -0
- package/dist/seed.d.ts +12 -0
- package/dist/seed.js +132 -0
- package/dist/seed.js.map +1 -0
- package/dist/serialize.d.ts +37 -0
- package/dist/serialize.js +165 -0
- package/dist/serialize.js.map +1 -0
- package/dist/service.d.ts +168 -0
- package/dist/service.js +757 -0
- package/dist/service.js.map +1 -0
- package/dist/teams.d.ts +63 -0
- package/dist/teams.js +131 -0
- package/dist/teams.js.map +1 -0
- package/dist/testing.d.ts +63 -0
- package/dist/testing.js +420 -0
- package/dist/testing.js.map +1 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @tekki-forge/runtime-api
|
|
2
|
+
|
|
3
|
+
Forge API runtime: generic, IR-driven CRUD on NestJS with auth, RBAC, tenancy, audit and hooks.
|
|
4
|
+
|
|
5
|
+
Part of [Tekki Forge](https://www.npmjs.com/package/tekki-forge), which turns a `forge.config.ts` into a production-ready full-stack app. Apps created with `forge new` install this package for you; see the [tekki-forge](https://www.npmjs.com/package/tekki-forge) package for the documentation.
|
package/dist/access.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { EntityIR, FieldIR } from "@tekki-forge/compiler";
|
|
2
|
+
import { type PolicyCheck, type Principal } from "@tekki-forge/runtime-permissions";
|
|
3
|
+
type Row = Record<string, any>;
|
|
4
|
+
/** Can `principal` read field `f` of this record? (Entity-level read is checked separately.) */
|
|
5
|
+
export declare function canReadField(entity: EntityIR, f: FieldIR, principal: Principal | null, record: Row, check?: PolicyCheck): Promise<boolean>;
|
|
6
|
+
/** Field names this caller may not read on this record. */
|
|
7
|
+
export declare function unreadableFields(entity: EntityIR, principal: Principal | null, record: Row, check?: PolicyCheck): Promise<Set<string>>;
|
|
8
|
+
/**
|
|
9
|
+
* Fields the caller may use in filters, sorting and search. A field restricted per record (owner/policy)
|
|
10
|
+
* cannot be queried, otherwise its values would leak through result counts.
|
|
11
|
+
*/
|
|
12
|
+
export declare function queryableFields(entity: EntityIR, principal: Principal | null): Set<string>;
|
|
13
|
+
/** Fields in `data` (Prisma-shaped write data) the caller is not allowed to write on `record`. */
|
|
14
|
+
export declare function unwritableFields(entity: EntityIR, data: Row, principal: Principal | null, record: Row, check?: PolicyCheck): Promise<FieldIR[]>;
|
|
15
|
+
export {};
|
package/dist/access.js
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { allowsRecord, evaluate } from "@tekki-forge/runtime-permissions";
|
|
2
|
+
/** Can `principal` read field `f` of this record? (Entity-level read is checked separately.) */
|
|
3
|
+
export async function canReadField(entity, f, principal, record, check) {
|
|
4
|
+
const rule = f.access?.read;
|
|
5
|
+
if (!rule)
|
|
6
|
+
return true;
|
|
7
|
+
return allowsRecord(entity, evaluate(rule, principal), principal, record, check);
|
|
8
|
+
}
|
|
9
|
+
/** Field names this caller may not read on this record. */
|
|
10
|
+
export async function unreadableFields(entity, principal, record, check) {
|
|
11
|
+
const out = new Set();
|
|
12
|
+
for (const f of entity.fields) {
|
|
13
|
+
if (f.access?.read && !(await canReadField(entity, f, principal, record, check)))
|
|
14
|
+
out.add(f.name);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Fields the caller may use in filters, sorting and search. A field restricted per record (owner/policy)
|
|
20
|
+
* cannot be queried, otherwise its values would leak through result counts.
|
|
21
|
+
*/
|
|
22
|
+
export function queryableFields(entity, principal) {
|
|
23
|
+
const out = new Set();
|
|
24
|
+
for (const f of entity.fields) {
|
|
25
|
+
const rule = f.access?.read;
|
|
26
|
+
if (!rule)
|
|
27
|
+
out.add(f.name);
|
|
28
|
+
else {
|
|
29
|
+
const d = evaluate(rule, principal);
|
|
30
|
+
if (d.allowed && d.scope === "all")
|
|
31
|
+
out.add(f.name);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
/** Fields in `data` (Prisma-shaped write data) the caller is not allowed to write on `record`. */
|
|
37
|
+
export async function unwritableFields(entity, data, principal, record, check) {
|
|
38
|
+
const denied = [];
|
|
39
|
+
for (const f of entity.fields) {
|
|
40
|
+
const rule = f.access?.write;
|
|
41
|
+
if (!rule)
|
|
42
|
+
continue;
|
|
43
|
+
const keys = f.kind === "location" ? [`${f.name}Lat`, `${f.name}Lng`] : f.relation?.type === "many-to-many" ? [f.name] : [f.apiName];
|
|
44
|
+
if (!keys.some((k) => k in data))
|
|
45
|
+
continue;
|
|
46
|
+
if (!(await allowsRecord(entity, evaluate(rule, principal), principal, record, check)))
|
|
47
|
+
denied.push(f);
|
|
48
|
+
}
|
|
49
|
+
return denied;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=access.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"access.js","sourceRoot":"","sources":["../src/access.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { AppIR } from "@tekki-forge/compiler";
|
|
2
|
+
import type { ForgeRequestContext } from "./extension.js";
|
|
3
|
+
type Row = Record<string, any>;
|
|
4
|
+
/**
|
|
5
|
+
* Scoped API keys: `Authorization: Bearer fk_<prefix>_<secret>`.
|
|
6
|
+
* The prefix (12 hex chars) is a public lookup id; only a SHA-256 hash of the secret is stored.
|
|
7
|
+
* A key is a service principal of its organization: the role chosen at creation (never above the
|
|
8
|
+
* creator's, re-checked on every request) intersected with its scopes.
|
|
9
|
+
*/
|
|
10
|
+
export declare const API_KEY_PATTERN: RegExp;
|
|
11
|
+
export declare function generateApiKey(): {
|
|
12
|
+
key: string;
|
|
13
|
+
prefix: string;
|
|
14
|
+
hash: string;
|
|
15
|
+
};
|
|
16
|
+
export declare function hashSecret(secret: string): string;
|
|
17
|
+
export declare function parseApiKey(token: string): {
|
|
18
|
+
prefix: string;
|
|
19
|
+
secret: string;
|
|
20
|
+
} | null;
|
|
21
|
+
/** Constant-time comparison of a presented secret with the stored hash. */
|
|
22
|
+
export declare function secretMatches(secret: string, hash: string): boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Validate and normalize scopes: `<resource>:read|write|*`, `*:read`, `*:write`, `*`.
|
|
25
|
+
* `<resource>` is an entity path (e.g. "invoices"). Returns the issues found.
|
|
26
|
+
*/
|
|
27
|
+
export declare function normalizeScopes(app: AppIR, input: unknown): {
|
|
28
|
+
scopes: string[];
|
|
29
|
+
issues: string[];
|
|
30
|
+
};
|
|
31
|
+
/** Simple fixed-window limiter per key, in memory (per API process — use a proxy limit too when you run several). */
|
|
32
|
+
export declare class RateLimiter {
|
|
33
|
+
readonly limit: number;
|
|
34
|
+
readonly windowMs: number;
|
|
35
|
+
private readonly windows;
|
|
36
|
+
constructor(limit: number, windowMs?: number);
|
|
37
|
+
/** Returns seconds to wait when over the limit, else 0. */
|
|
38
|
+
hit(key: string, now?: number): number;
|
|
39
|
+
}
|
|
40
|
+
export interface ApiKeyInput {
|
|
41
|
+
name: string;
|
|
42
|
+
scopes: unknown;
|
|
43
|
+
role: string;
|
|
44
|
+
/** ISO date or Date; null/undefined = never. */
|
|
45
|
+
expiresAt?: string | Date | null;
|
|
46
|
+
organizationId: string | null;
|
|
47
|
+
createdById: string;
|
|
48
|
+
}
|
|
49
|
+
/** Public view of a key (never the hash). */
|
|
50
|
+
export declare function presentApiKey(k: Row): {
|
|
51
|
+
id: any;
|
|
52
|
+
name: any;
|
|
53
|
+
prefix: any;
|
|
54
|
+
scopes: any;
|
|
55
|
+
role: any;
|
|
56
|
+
expiresAt: any;
|
|
57
|
+
lastUsedAt: any;
|
|
58
|
+
revokedAt: any;
|
|
59
|
+
createdAt: any;
|
|
60
|
+
createdBy: {
|
|
61
|
+
id: any;
|
|
62
|
+
name: any;
|
|
63
|
+
email: any;
|
|
64
|
+
} | null;
|
|
65
|
+
status: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Create a key (used by the admin API and `forge apikey create`). `creatorRoles` are the creator's
|
|
69
|
+
* effective roles: the key's role may not exceed them. Returns the plain key once.
|
|
70
|
+
*/
|
|
71
|
+
export declare function issueApiKey(app: AppIR, prisma: any, input: ApiKeyInput, creatorRoles: string[]): Promise<{
|
|
72
|
+
key: string;
|
|
73
|
+
apiKey: ReturnType<typeof presentApiKey>;
|
|
74
|
+
}>;
|
|
75
|
+
export interface ApiKeyServiceOptions {
|
|
76
|
+
/** Requests per minute per key (FORGE_API_KEY_RATE_LIMIT, default 600; 0 disables). */
|
|
77
|
+
rateLimit?: number;
|
|
78
|
+
}
|
|
79
|
+
export declare class ApiKeyService {
|
|
80
|
+
private readonly app;
|
|
81
|
+
private readonly prisma;
|
|
82
|
+
readonly limiter: RateLimiter;
|
|
83
|
+
constructor(app: AppIR, prisma: any, options?: ApiKeyServiceOptions);
|
|
84
|
+
/** Request context for a bearer token; 401 for unknown, revoked or expired keys, 429 over the rate limit. */
|
|
85
|
+
resolve(token: string, ip?: string): Promise<ForgeRequestContext>;
|
|
86
|
+
private adminScope;
|
|
87
|
+
list(ctx: ForgeRequestContext): Promise<{
|
|
88
|
+
data: {
|
|
89
|
+
id: any;
|
|
90
|
+
name: any;
|
|
91
|
+
prefix: any;
|
|
92
|
+
scopes: any;
|
|
93
|
+
role: any;
|
|
94
|
+
expiresAt: any;
|
|
95
|
+
lastUsedAt: any;
|
|
96
|
+
revokedAt: any;
|
|
97
|
+
createdAt: any;
|
|
98
|
+
createdBy: {
|
|
99
|
+
id: any;
|
|
100
|
+
name: any;
|
|
101
|
+
email: any;
|
|
102
|
+
} | null;
|
|
103
|
+
status: string;
|
|
104
|
+
}[];
|
|
105
|
+
}>;
|
|
106
|
+
create(body: Row, ctx: ForgeRequestContext): Promise<{
|
|
107
|
+
key: string;
|
|
108
|
+
apiKey: ReturnType<typeof presentApiKey>;
|
|
109
|
+
}>;
|
|
110
|
+
revoke(id: string, ctx: ForgeRequestContext): Promise<{
|
|
111
|
+
id: any;
|
|
112
|
+
name: any;
|
|
113
|
+
prefix: any;
|
|
114
|
+
scopes: any;
|
|
115
|
+
role: any;
|
|
116
|
+
expiresAt: any;
|
|
117
|
+
lastUsedAt: any;
|
|
118
|
+
revokedAt: any;
|
|
119
|
+
createdAt: any;
|
|
120
|
+
createdBy: {
|
|
121
|
+
id: any;
|
|
122
|
+
name: any;
|
|
123
|
+
email: any;
|
|
124
|
+
} | null;
|
|
125
|
+
status: string;
|
|
126
|
+
}>;
|
|
127
|
+
}
|
|
128
|
+
/** "30d", "12w", "6m", "1y" or an ISO date → Date. */
|
|
129
|
+
export declare function parseExpiry(v: string, now?: Date): Date | null;
|
|
130
|
+
/**
|
|
131
|
+
* `forge apikey create|list|revoke` against the app's database (apps/api/src/scripts/apikey.ts).
|
|
132
|
+
* Returns the process exit code.
|
|
133
|
+
*/
|
|
134
|
+
export declare function runApiKeyCommand(argv: string[], deps: {
|
|
135
|
+
app: AppIR;
|
|
136
|
+
prisma: any;
|
|
137
|
+
log?: (line: string) => void;
|
|
138
|
+
error?: (line: string) => void;
|
|
139
|
+
}): Promise<number>;
|
|
140
|
+
export {};
|
package/dist/api-keys.js
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import { effectiveRoles } from "@tekki-forge/runtime-permissions";
|
|
4
|
+
import { BadRequestException, ForbiddenException, HttpException, NotFoundException, UnauthorizedException } from "@nestjs/common";
|
|
5
|
+
/**
|
|
6
|
+
* Scoped API keys: `Authorization: Bearer fk_<prefix>_<secret>`.
|
|
7
|
+
* The prefix (12 hex chars) is a public lookup id; only a SHA-256 hash of the secret is stored.
|
|
8
|
+
* A key is a service principal of its organization: the role chosen at creation (never above the
|
|
9
|
+
* creator's, re-checked on every request) intersected with its scopes.
|
|
10
|
+
*/
|
|
11
|
+
export const API_KEY_PATTERN = /^fk_([0-9a-f]{12})_([A-Za-z0-9_-]{32,64})$/;
|
|
12
|
+
export function generateApiKey() {
|
|
13
|
+
const prefix = randomBytes(6).toString("hex");
|
|
14
|
+
const secret = randomBytes(24).toString("base64url");
|
|
15
|
+
return { key: `fk_${prefix}_${secret}`, prefix, hash: hashSecret(secret) };
|
|
16
|
+
}
|
|
17
|
+
export function hashSecret(secret) {
|
|
18
|
+
return createHash("sha256").update(secret, "utf8").digest("hex");
|
|
19
|
+
}
|
|
20
|
+
export function parseApiKey(token) {
|
|
21
|
+
const m = API_KEY_PATTERN.exec(token.trim());
|
|
22
|
+
return m ? { prefix: m[1], secret: m[2] } : null;
|
|
23
|
+
}
|
|
24
|
+
/** Constant-time comparison of a presented secret with the stored hash. */
|
|
25
|
+
export function secretMatches(secret, hash) {
|
|
26
|
+
const a = Buffer.from(hashSecret(secret), "hex");
|
|
27
|
+
const b = Buffer.from(hash, "hex");
|
|
28
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validate and normalize scopes: `<resource>:read|write|*`, `*:read`, `*:write`, `*`.
|
|
32
|
+
* `<resource>` is an entity path (e.g. "invoices"). Returns the issues found.
|
|
33
|
+
*/
|
|
34
|
+
export function normalizeScopes(app, input) {
|
|
35
|
+
const raw = Array.isArray(input) ? input : typeof input === "string" ? input.split(/[\s,]+/) : [];
|
|
36
|
+
const paths = new Set(app.entities.map((e) => e.path));
|
|
37
|
+
const scopes = [];
|
|
38
|
+
const issues = [];
|
|
39
|
+
for (const item of raw) {
|
|
40
|
+
const s = String(item).trim();
|
|
41
|
+
if (!s)
|
|
42
|
+
continue;
|
|
43
|
+
const m = /^([a-z0-9*][a-z0-9-]*|\*):(read|write|\*)$/.exec(s);
|
|
44
|
+
if (s === "*")
|
|
45
|
+
scopes.push(s);
|
|
46
|
+
else if (!m)
|
|
47
|
+
issues.push(`"${s}" is not a scope — use <resource>:read, <resource>:write, *:read or *`);
|
|
48
|
+
else if (m[1] !== "*" && !paths.has(m[1]))
|
|
49
|
+
issues.push(`unknown resource "${m[1]}" in "${s}" (resources: ${[...paths].join(", ")})`);
|
|
50
|
+
else
|
|
51
|
+
scopes.push(s);
|
|
52
|
+
}
|
|
53
|
+
if (!scopes.length && !issues.length)
|
|
54
|
+
issues.push("at least one scope is required, e.g. invoices:read");
|
|
55
|
+
return { scopes: [...new Set(scopes)], issues };
|
|
56
|
+
}
|
|
57
|
+
/** Simple fixed-window limiter per key, in memory (per API process — use a proxy limit too when you run several). */
|
|
58
|
+
export class RateLimiter {
|
|
59
|
+
limit;
|
|
60
|
+
windowMs;
|
|
61
|
+
windows = new Map();
|
|
62
|
+
constructor(limit, windowMs = 60_000) {
|
|
63
|
+
this.limit = limit;
|
|
64
|
+
this.windowMs = windowMs;
|
|
65
|
+
}
|
|
66
|
+
/** Returns seconds to wait when over the limit, else 0. */
|
|
67
|
+
hit(key, now = Date.now()) {
|
|
68
|
+
if (this.limit <= 0)
|
|
69
|
+
return 0;
|
|
70
|
+
let w = this.windows.get(key);
|
|
71
|
+
if (!w || now - w.start >= this.windowMs) {
|
|
72
|
+
w = { start: now, count: 0 };
|
|
73
|
+
this.windows.set(key, w);
|
|
74
|
+
if (this.windows.size > 10_000)
|
|
75
|
+
for (const [k, v] of this.windows)
|
|
76
|
+
if (now - v.start >= this.windowMs)
|
|
77
|
+
this.windows.delete(k);
|
|
78
|
+
}
|
|
79
|
+
w.count++;
|
|
80
|
+
return w.count > this.limit ? Math.ceil((w.start + this.windowMs - now) / 1000) : 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const invalid = (issues) => new BadRequestException({ statusCode: 400, error: "Bad Request", message: "Validation failed", issues });
|
|
84
|
+
/** Public view of a key (never the hash). */
|
|
85
|
+
export function presentApiKey(k) {
|
|
86
|
+
return {
|
|
87
|
+
id: k.id,
|
|
88
|
+
name: k.name,
|
|
89
|
+
prefix: k.prefix,
|
|
90
|
+
scopes: k.scopes,
|
|
91
|
+
role: k.role,
|
|
92
|
+
expiresAt: k.expiresAt ?? null,
|
|
93
|
+
lastUsedAt: k.lastUsedAt ?? null,
|
|
94
|
+
revokedAt: k.revokedAt ?? null,
|
|
95
|
+
createdAt: k.createdAt,
|
|
96
|
+
createdBy: k.createdBy ? { id: k.createdBy.id, name: k.createdBy.name, email: k.createdBy.email } : null,
|
|
97
|
+
status: k.revokedAt ? "revoked" : k.expiresAt && new Date(k.expiresAt).getTime() <= Date.now() ? "expired" : "active",
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Create a key (used by the admin API and `forge apikey create`). `creatorRoles` are the creator's
|
|
102
|
+
* effective roles: the key's role may not exceed them. Returns the plain key once.
|
|
103
|
+
*/
|
|
104
|
+
export async function issueApiKey(app, prisma, input, creatorRoles) {
|
|
105
|
+
const issues = [];
|
|
106
|
+
const name = typeof input.name === "string" ? input.name.trim().slice(0, 100) : "";
|
|
107
|
+
if (!name)
|
|
108
|
+
issues.push({ path: "name", message: "Required" });
|
|
109
|
+
const { scopes, issues: scopeIssues } = normalizeScopes(app, input.scopes);
|
|
110
|
+
for (const m of scopeIssues)
|
|
111
|
+
issues.push({ path: "scopes", message: m });
|
|
112
|
+
const role = typeof input.role === "string" ? input.role.trim() : "";
|
|
113
|
+
const assignable = app.roles.filter((r) => r !== "user" || !app.tenancy.enabled);
|
|
114
|
+
if (!assignable.includes(role))
|
|
115
|
+
issues.push({ path: "role", message: `must be one of ${assignable.join(", ")}` });
|
|
116
|
+
else if (!creatorRoles.includes("admin") && !creatorRoles.includes(role))
|
|
117
|
+
issues.push({ path: "role", message: `you do not have the "${role}" role yourself` });
|
|
118
|
+
let expiresAt = null;
|
|
119
|
+
if (input.expiresAt) {
|
|
120
|
+
expiresAt = input.expiresAt instanceof Date ? input.expiresAt : new Date(String(input.expiresAt));
|
|
121
|
+
if (Number.isNaN(expiresAt.getTime()))
|
|
122
|
+
issues.push({ path: "expiresAt", message: "must be a date" });
|
|
123
|
+
else if (expiresAt.getTime() <= Date.now())
|
|
124
|
+
issues.push({ path: "expiresAt", message: "must be in the future" });
|
|
125
|
+
}
|
|
126
|
+
if (app.tenancy.enabled && !input.organizationId)
|
|
127
|
+
issues.push({ path: "organizationId", message: "an organization is required" });
|
|
128
|
+
if (issues.length)
|
|
129
|
+
throw invalid(issues);
|
|
130
|
+
const { key, prefix, hash } = generateApiKey();
|
|
131
|
+
const row = await prisma.apiKey.create({
|
|
132
|
+
data: {
|
|
133
|
+
name,
|
|
134
|
+
prefix,
|
|
135
|
+
hash,
|
|
136
|
+
scopes,
|
|
137
|
+
role,
|
|
138
|
+
expiresAt,
|
|
139
|
+
createdById: input.createdById,
|
|
140
|
+
...(app.tenancy.enabled ? { organizationId: input.organizationId } : {}),
|
|
141
|
+
},
|
|
142
|
+
include: { createdBy: { select: { id: true, name: true, email: true } } },
|
|
143
|
+
});
|
|
144
|
+
return { key, apiKey: presentApiKey(row) };
|
|
145
|
+
}
|
|
146
|
+
export class ApiKeyService {
|
|
147
|
+
app;
|
|
148
|
+
prisma;
|
|
149
|
+
limiter;
|
|
150
|
+
constructor(app, prisma, options = {}) {
|
|
151
|
+
this.app = app;
|
|
152
|
+
this.prisma = prisma;
|
|
153
|
+
const env = Number(process.env.FORGE_API_KEY_RATE_LIMIT);
|
|
154
|
+
this.limiter = new RateLimiter(options.rateLimit ?? (Number.isFinite(env) && process.env.FORGE_API_KEY_RATE_LIMIT !== "" ? env : 600));
|
|
155
|
+
}
|
|
156
|
+
/** Request context for a bearer token; 401 for unknown, revoked or expired keys, 429 over the rate limit. */
|
|
157
|
+
async resolve(token, ip) {
|
|
158
|
+
const parsed = parseApiKey(token);
|
|
159
|
+
const deny = () => new UnauthorizedException("Invalid, expired or revoked API key");
|
|
160
|
+
if (!parsed)
|
|
161
|
+
throw deny();
|
|
162
|
+
const key = await this.prisma.apiKey.findUnique({ where: { prefix: parsed.prefix }, include: { createdBy: { select: { id: true, role: true, banned: true } } } });
|
|
163
|
+
if (!key || !secretMatches(parsed.secret, key.hash))
|
|
164
|
+
throw deny();
|
|
165
|
+
if (key.revokedAt || (key.expiresAt && new Date(key.expiresAt).getTime() <= Date.now()))
|
|
166
|
+
throw deny();
|
|
167
|
+
// The key never exceeds its creator: a creator who left the organization, was banned or deleted disables it.
|
|
168
|
+
const creator = key.createdBy;
|
|
169
|
+
if (!creator || creator.banned)
|
|
170
|
+
throw deny();
|
|
171
|
+
const tenancy = this.app.tenancy.enabled;
|
|
172
|
+
let creatorRoles;
|
|
173
|
+
if (tenancy) {
|
|
174
|
+
const member = key.organizationId ? await this.prisma.member.findFirst({ where: { organizationId: key.organizationId, userId: creator.id } }) : null;
|
|
175
|
+
if (!member)
|
|
176
|
+
throw deny();
|
|
177
|
+
creatorRoles = effectiveRoles(creator.role, member.role, true);
|
|
178
|
+
}
|
|
179
|
+
else
|
|
180
|
+
creatorRoles = effectiveRoles(creator.role, null, false);
|
|
181
|
+
const keyRoles = tenancy ? effectiveRoles(null, key.role, true) : effectiveRoles(key.role, null, false);
|
|
182
|
+
const roles = creatorRoles.includes("admin") ? keyRoles : keyRoles.filter((r) => creatorRoles.includes(r));
|
|
183
|
+
const wait = this.limiter.hit(key.id);
|
|
184
|
+
if (wait > 0)
|
|
185
|
+
throw new HttpException({ statusCode: 429, error: "Too Many Requests", message: `Rate limit of ${this.limiter.limit} requests per minute exceeded — retry in ${wait}s` }, 429);
|
|
186
|
+
if (!key.lastUsedAt || Date.now() - new Date(key.lastUsedAt).getTime() > 60_000) {
|
|
187
|
+
await this.prisma.apiKey.update({ where: { id: key.id }, data: { lastUsedAt: new Date() } }).catch(() => undefined);
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
principal: { id: `apikey:${key.id}`, roles, organizationId: tenancy ? key.organizationId : null, scopes: key.scopes, apiKeyId: key.id, teamIds: [] },
|
|
191
|
+
user: null,
|
|
192
|
+
session: null,
|
|
193
|
+
apiKey: { id: key.id, name: key.name, prefix: key.prefix, createdById: creator.id },
|
|
194
|
+
...(ip ? { ip } : {}),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
adminScope(ctx) {
|
|
198
|
+
if (!ctx.principal || !ctx.user)
|
|
199
|
+
throw new UnauthorizedException("Sign in to continue");
|
|
200
|
+
if (ctx.principal.apiKeyId)
|
|
201
|
+
throw new ForbiddenException("API keys cannot manage API keys");
|
|
202
|
+
if (!ctx.principal.roles.includes("admin"))
|
|
203
|
+
throw new ForbiddenException("Only admins can manage API keys");
|
|
204
|
+
if (this.app.tenancy.enabled) {
|
|
205
|
+
if (!ctx.principal.organizationId)
|
|
206
|
+
throw new ForbiddenException("No active organization — create or select an organization first");
|
|
207
|
+
return { organizationId: ctx.principal.organizationId };
|
|
208
|
+
}
|
|
209
|
+
return {};
|
|
210
|
+
}
|
|
211
|
+
async list(ctx) {
|
|
212
|
+
const where = this.adminScope(ctx);
|
|
213
|
+
const rows = await this.prisma.apiKey.findMany({ where, orderBy: { createdAt: "desc" }, include: { createdBy: { select: { id: true, name: true, email: true } } } });
|
|
214
|
+
return { data: rows.map(presentApiKey) };
|
|
215
|
+
}
|
|
216
|
+
async create(body, ctx) {
|
|
217
|
+
this.adminScope(ctx);
|
|
218
|
+
return issueApiKey(this.app, this.prisma, { name: body?.name, scopes: body?.scopes, role: body?.role, expiresAt: body?.expiresAt ?? null, organizationId: ctx.principal.organizationId ?? null, createdById: ctx.user.id }, ctx.principal.roles);
|
|
219
|
+
}
|
|
220
|
+
async revoke(id, ctx) {
|
|
221
|
+
const where = this.adminScope(ctx);
|
|
222
|
+
const key = /^[0-9a-f-]{36}$/i.test(id) ? await this.prisma.apiKey.findFirst({ where: { ...where, id } }) : null;
|
|
223
|
+
if (!key)
|
|
224
|
+
throw new NotFoundException("API key not found");
|
|
225
|
+
const row = key.revokedAt ? key : await this.prisma.apiKey.update({ where: { id }, data: { revokedAt: new Date() } });
|
|
226
|
+
return presentApiKey(row);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** "30d", "12w", "6m", "1y" or an ISO date → Date. */
|
|
230
|
+
export function parseExpiry(v, now = new Date()) {
|
|
231
|
+
const m = /^(\d+)([dwmy])$/.exec(v.trim());
|
|
232
|
+
if (m) {
|
|
233
|
+
const d = new Date(now);
|
|
234
|
+
const n = Number(m[1]);
|
|
235
|
+
if (m[2] === "d")
|
|
236
|
+
d.setUTCDate(d.getUTCDate() + n);
|
|
237
|
+
if (m[2] === "w")
|
|
238
|
+
d.setUTCDate(d.getUTCDate() + 7 * n);
|
|
239
|
+
if (m[2] === "m")
|
|
240
|
+
d.setUTCMonth(d.getUTCMonth() + n);
|
|
241
|
+
if (m[2] === "y")
|
|
242
|
+
d.setUTCFullYear(d.getUTCFullYear() + n);
|
|
243
|
+
return d;
|
|
244
|
+
}
|
|
245
|
+
const d = new Date(v);
|
|
246
|
+
return Number.isNaN(d.getTime()) ? null : d;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* `forge apikey create|list|revoke` against the app's database (apps/api/src/scripts/apikey.ts).
|
|
250
|
+
* Returns the process exit code.
|
|
251
|
+
*/
|
|
252
|
+
export async function runApiKeyCommand(argv, deps) {
|
|
253
|
+
const log = deps.log ?? ((l) => console.log(l));
|
|
254
|
+
const error = deps.error ?? ((l) => console.error(l));
|
|
255
|
+
const { app, prisma } = deps;
|
|
256
|
+
const { positionals, values } = parseArgs({
|
|
257
|
+
args: argv,
|
|
258
|
+
allowPositionals: true,
|
|
259
|
+
options: { name: { type: "string" }, scopes: { type: "string" }, role: { type: "string" }, expires: { type: "string" }, org: { type: "string" }, user: { type: "string" }, json: { type: "boolean", default: false } },
|
|
260
|
+
});
|
|
261
|
+
const [command, target] = positionals;
|
|
262
|
+
const usage = "Usage: forge apikey create --name <name> --scopes <invoices:read,*:read> [--role member] [--expires 90d|2027-01-01] [--org <slug>] [--user <creator email>]\n forge apikey list [--org <slug>]\n forge apikey revoke <id|prefix>";
|
|
263
|
+
const findOrg = async () => {
|
|
264
|
+
if (!app.tenancy.enabled)
|
|
265
|
+
return null;
|
|
266
|
+
if (values.org) {
|
|
267
|
+
const org = await prisma.organization.findFirst({ where: { OR: [{ slug: values.org }, { id: values.org }] } });
|
|
268
|
+
if (!org)
|
|
269
|
+
throw new Error(`No organization "${values.org}"`);
|
|
270
|
+
return org;
|
|
271
|
+
}
|
|
272
|
+
const orgs = await prisma.organization.findMany({ take: 10, orderBy: { createdAt: "asc" } });
|
|
273
|
+
if (orgs.length === 1)
|
|
274
|
+
return orgs[0];
|
|
275
|
+
throw new Error(orgs.length ? `Several organizations — pass --org (${orgs.map((o) => o.slug).join(", ")})` : "No organization yet — sign up first");
|
|
276
|
+
};
|
|
277
|
+
try {
|
|
278
|
+
if (command === "create") {
|
|
279
|
+
if (!values.name || !values.scopes) {
|
|
280
|
+
error(usage);
|
|
281
|
+
return 1;
|
|
282
|
+
}
|
|
283
|
+
const org = await findOrg();
|
|
284
|
+
// The creator: --user, else the first admin of the organization (or a global admin without tenancy).
|
|
285
|
+
let creator = null;
|
|
286
|
+
if (values.user)
|
|
287
|
+
creator = await prisma.user.findUnique({ where: { email: values.user } });
|
|
288
|
+
else if (org)
|
|
289
|
+
creator = (await prisma.member.findFirst({ where: { organizationId: org.id, role: { in: ["owner", "admin"] } }, orderBy: { createdAt: "asc" }, include: { user: true } }))?.user ?? null;
|
|
290
|
+
else
|
|
291
|
+
creator = await prisma.user.findFirst({ where: { role: { contains: "admin" } }, orderBy: { createdAt: "asc" } });
|
|
292
|
+
if (!creator)
|
|
293
|
+
throw new Error(values.user ? `No user ${values.user}` : "No admin user found — pass --user <email>");
|
|
294
|
+
let creatorRoles;
|
|
295
|
+
if (org) {
|
|
296
|
+
const member = await prisma.member.findFirst({ where: { organizationId: org.id, userId: creator.id } });
|
|
297
|
+
if (!member)
|
|
298
|
+
throw new Error(`${creator.email} is not a member of ${org.slug}`);
|
|
299
|
+
creatorRoles = effectiveRoles(creator.role, member.role, true);
|
|
300
|
+
}
|
|
301
|
+
else
|
|
302
|
+
creatorRoles = effectiveRoles(creator.role, null, false);
|
|
303
|
+
const expiresAt = values.expires ? parseExpiry(values.expires) : null;
|
|
304
|
+
if (values.expires && !expiresAt)
|
|
305
|
+
throw new Error(`--expires: "${values.expires}" is not a date or a duration like 90d`);
|
|
306
|
+
const { key, apiKey } = await issueApiKey(app, prisma, { name: values.name, scopes: values.scopes, role: values.role ?? (app.tenancy.enabled ? "member" : "user"), expiresAt, organizationId: org?.id ?? null, createdById: creator.id }, creatorRoles);
|
|
307
|
+
if (values.json)
|
|
308
|
+
log(JSON.stringify({ key, apiKey }));
|
|
309
|
+
else {
|
|
310
|
+
const expires = apiKey.expiresAt ? `, expires ${new Date(apiKey.expiresAt).toISOString().slice(0, 10)}` : "";
|
|
311
|
+
log(`Created API key "${apiKey.name}" (${apiKey.id}) — role ${apiKey.role}, scopes ${apiKey.scopes.join(" ")}${expires}${org ? `, organization ${org.slug}` : ""}`);
|
|
312
|
+
log("");
|
|
313
|
+
log(` ${key}`);
|
|
314
|
+
log("");
|
|
315
|
+
log("Store it now: it is not shown again. Send it as `Authorization: Bearer <key>`.");
|
|
316
|
+
}
|
|
317
|
+
return 0;
|
|
318
|
+
}
|
|
319
|
+
if (command === "list") {
|
|
320
|
+
// The operator sees every key unless --org narrows it.
|
|
321
|
+
const org = values.org ? await findOrg() : null;
|
|
322
|
+
const rows = await prisma.apiKey.findMany({ where: org ? { organizationId: org.id } : {}, orderBy: { createdAt: "desc" }, include: { createdBy: { select: { id: true, name: true, email: true } } } });
|
|
323
|
+
const keys = rows.map(presentApiKey);
|
|
324
|
+
if (values.json)
|
|
325
|
+
log(JSON.stringify(keys));
|
|
326
|
+
else if (!keys.length)
|
|
327
|
+
log("No API keys.");
|
|
328
|
+
else
|
|
329
|
+
for (const k of keys)
|
|
330
|
+
log(`${k.id} fk_${k.prefix}_… ${k.status.padEnd(7)} ${k.name} role=${k.role} scopes=${k.scopes.join(",")} last used ${k.lastUsedAt ? new Date(k.lastUsedAt).toISOString() : "never"}`);
|
|
331
|
+
return 0;
|
|
332
|
+
}
|
|
333
|
+
if (command === "revoke") {
|
|
334
|
+
if (!target) {
|
|
335
|
+
error(usage);
|
|
336
|
+
return 1;
|
|
337
|
+
}
|
|
338
|
+
const prefix = /^fk_([0-9a-f]{12})/.exec(target)?.[1] ?? (/^[0-9a-f]{12}$/.test(target) ? target : null);
|
|
339
|
+
let where = null;
|
|
340
|
+
if (prefix)
|
|
341
|
+
where = { prefix };
|
|
342
|
+
else if (/^[0-9a-f-]{36}$/i.test(target))
|
|
343
|
+
where = { id: target };
|
|
344
|
+
const key = where ? await prisma.apiKey.findFirst({ where }) : null;
|
|
345
|
+
if (!key)
|
|
346
|
+
throw new Error(`No API key ${target}`);
|
|
347
|
+
if (!key.revokedAt)
|
|
348
|
+
await prisma.apiKey.update({ where: { id: key.id }, data: { revokedAt: new Date() } });
|
|
349
|
+
log(`Revoked API key "${key.name}" (fk_${key.prefix}_…)`);
|
|
350
|
+
return 0;
|
|
351
|
+
}
|
|
352
|
+
error(usage);
|
|
353
|
+
return 1;
|
|
354
|
+
}
|
|
355
|
+
catch (e) {
|
|
356
|
+
const issues = e.getResponse?.()?.issues;
|
|
357
|
+
error(issues?.length ? issues.map((i) => `${i.path}: ${i.message}`).join("\n") : e.message);
|
|
358
|
+
return 1;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../src/api-keys.ts"],"names":[],"mappings":"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"}
|