@aooth/auth 0.1.7 → 0.1.8
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/dist/atscript-db.cjs +36 -25
- package/dist/atscript-db.d.cts +28 -22
- package/dist/atscript-db.d.mts +28 -22
- package/dist/atscript-db.mjs +36 -25
- package/dist/authz.cjs +168 -0
- package/dist/authz.d.cts +252 -0
- package/dist/authz.d.mts +252 -0
- package/dist/authz.mjs +161 -0
- package/dist/client.cjs +59 -0
- package/dist/client.d.cts +64 -0
- package/dist/client.d.mts +64 -0
- package/dist/client.mjs +58 -0
- package/dist/clock-Bdsep_1j.mjs +4 -0
- package/dist/clock-BjXa0LXb.d.cts +14 -0
- package/dist/clock-BjXa0LXb.d.mts +14 -0
- package/dist/clock-Bl-H3eqE.cjs +9 -0
- package/dist/index.cjs +294 -65
- package/dist/index.d.cts +166 -44
- package/dist/index.d.mts +166 -44
- package/dist/index.mjs +289 -60
- package/dist/payload-BJjvj8AH.cjs +32 -0
- package/dist/payload-D-DzH5-J.mjs +27 -0
- package/dist/redis.cjs +9 -0
- package/dist/redis.d.cts +8 -7
- package/dist/redis.d.mts +8 -7
- package/dist/redis.mjs +9 -0
- package/dist/store-BG6m6oSJ.d.cts +263 -0
- package/dist/store-BG6m6oSJ.d.mts +263 -0
- package/package.json +28 -10
- package/src/atscript-db/auth-credential.as +16 -10
- package/src/atscript-db/auth-credential.as.d.ts +68 -0
- package/dist/store-B1t8KkfA.d.mts +0 -124
- package/dist/store-untAtWQz.d.cts +0 -124
package/dist/atscript-db.cjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_payload = require("./payload-BJjvj8AH.cjs");
|
|
2
3
|
let node_crypto = require("node:crypto");
|
|
3
4
|
//#region src/atscript-db/index.ts
|
|
4
5
|
/**
|
|
@@ -22,18 +23,7 @@ var CredentialStoreAtscriptDb = class {
|
|
|
22
23
|
async persist(state, ttl) {
|
|
23
24
|
const token = (0, node_crypto.randomUUID)();
|
|
24
25
|
const expiresAt = typeof ttl === "number" ? Date.now() + ttl : state.expiresAt;
|
|
25
|
-
|
|
26
|
-
token,
|
|
27
|
-
userId: state.userId,
|
|
28
|
-
issuedAt: state.issuedAt,
|
|
29
|
-
expiresAt,
|
|
30
|
-
kind: state.kind,
|
|
31
|
-
claims: state.claims,
|
|
32
|
-
metadata: state.metadata,
|
|
33
|
-
parentCredentialId: state.parentCredentialId,
|
|
34
|
-
rotatedAt: state.rotatedAt
|
|
35
|
-
};
|
|
36
|
-
await this.table.insertOne(row);
|
|
26
|
+
await this.table.insertOne(stateToRow(state, token, expiresAt));
|
|
37
27
|
return token;
|
|
38
28
|
}
|
|
39
29
|
async retrieve(token) {
|
|
@@ -57,23 +47,21 @@ var CredentialStoreAtscriptDb = class {
|
|
|
57
47
|
await this.revoke(token);
|
|
58
48
|
return token;
|
|
59
49
|
}
|
|
60
|
-
|
|
61
|
-
token,
|
|
62
|
-
userId: state.userId,
|
|
63
|
-
issuedAt: state.issuedAt,
|
|
64
|
-
expiresAt: state.expiresAt,
|
|
65
|
-
kind: state.kind,
|
|
66
|
-
claims: state.claims,
|
|
67
|
-
metadata: state.metadata,
|
|
68
|
-
parentCredentialId: state.parentCredentialId,
|
|
69
|
-
rotatedAt: state.rotatedAt
|
|
70
|
-
};
|
|
71
|
-
await this.table.replaceOne(row);
|
|
50
|
+
await this.table.replaceOne(stateToRow(state, token, state.expiresAt));
|
|
72
51
|
return token;
|
|
73
52
|
}
|
|
74
53
|
async revoke(token) {
|
|
75
54
|
await this.table.deleteOne(token);
|
|
76
55
|
}
|
|
56
|
+
async touch(token, at) {
|
|
57
|
+
const row = await this.table.findOne({ filter: { token } });
|
|
58
|
+
if (!row) return;
|
|
59
|
+
if (row.expiresAt <= Date.now()) return;
|
|
60
|
+
await this.table.replaceOne({
|
|
61
|
+
...row,
|
|
62
|
+
lastSeenAt: at
|
|
63
|
+
});
|
|
64
|
+
}
|
|
77
65
|
async revokeAllForUser(userId) {
|
|
78
66
|
return (await this.table.deleteMany({ userId })).deletedCount;
|
|
79
67
|
}
|
|
@@ -96,17 +84,40 @@ var CredentialStoreAtscriptDb = class {
|
|
|
96
84
|
return out;
|
|
97
85
|
}
|
|
98
86
|
};
|
|
87
|
+
/**
|
|
88
|
+
* Build the persisted row from a credential state: typed payload columns first
|
|
89
|
+
* (so an envelope field wins a name clash), then the fixed envelope fields and
|
|
90
|
+
* the resolved `token` + `expiresAt`. Shared by `persist` and `update`, which
|
|
91
|
+
* differ only in the `expiresAt` they resolve.
|
|
92
|
+
*/
|
|
93
|
+
function stateToRow(state, token, expiresAt) {
|
|
94
|
+
return {
|
|
95
|
+
...require_payload.credentialPayloadOf(state),
|
|
96
|
+
token,
|
|
97
|
+
userId: state.userId,
|
|
98
|
+
issuedAt: state.issuedAt,
|
|
99
|
+
expiresAt,
|
|
100
|
+
kind: state.kind,
|
|
101
|
+
metadata: state.metadata,
|
|
102
|
+
parentCredentialId: state.parentCredentialId,
|
|
103
|
+
rotatedAt: state.rotatedAt,
|
|
104
|
+
sessionId: state.sessionId,
|
|
105
|
+
lastSeenAt: state.lastSeenAt
|
|
106
|
+
};
|
|
107
|
+
}
|
|
99
108
|
function rowToState(row) {
|
|
100
109
|
const state = {
|
|
110
|
+
...require_payload.credentialPayloadOf(row),
|
|
101
111
|
userId: row.userId,
|
|
102
112
|
issuedAt: row.issuedAt,
|
|
103
113
|
expiresAt: row.expiresAt
|
|
104
114
|
};
|
|
105
|
-
if (row.claims !== void 0) state.claims = row.claims;
|
|
106
115
|
if (row.metadata !== void 0) state.metadata = row.metadata;
|
|
107
116
|
if (row.kind === "access" || row.kind === "refresh") state.kind = row.kind;
|
|
108
117
|
if (row.parentCredentialId !== void 0) state.parentCredentialId = row.parentCredentialId;
|
|
109
118
|
if (row.rotatedAt !== void 0) state.rotatedAt = row.rotatedAt;
|
|
119
|
+
if (row.sessionId !== void 0) state.sessionId = row.sessionId;
|
|
120
|
+
if (row.lastSeenAt !== void 0) state.lastSeenAt = row.lastSeenAt;
|
|
110
121
|
return state;
|
|
111
122
|
}
|
|
112
123
|
//#endregion
|
package/dist/atscript-db.d.cts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import { a as CredentialState, t as CredentialStore } from "./store-
|
|
1
|
+
import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.cjs";
|
|
2
2
|
|
|
3
3
|
//#region src/atscript-db/index.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Persisted row shape — mirrors `AoothAuthCredential` from
|
|
6
|
-
* `./auth-credential.as`. Re-declared here as a plain TS
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* `./auth-credential.as`. Re-declared here as a plain TS type so consumers can
|
|
7
|
+
* use the adapter without running the atscript build (and so `@aooth/auth`
|
|
8
|
+
* doesn't need to depend on `@atscript/typescript` at build time). When you DO
|
|
9
|
+
* wire the `.as` model, the shapes match by construction.
|
|
10
|
+
*
|
|
11
|
+
* The consumer's typed payload `TPayload` (the root fields they add to their
|
|
12
|
+
* `extends AoothAuthCredential` model) is intersected flat — those become real
|
|
13
|
+
* typed columns, replacing the dropped free-form `claims` blob.
|
|
10
14
|
*/
|
|
11
|
-
|
|
15
|
+
type AuthCredentialRow<TPayload extends object = object> = {
|
|
12
16
|
token: string;
|
|
13
17
|
userId: string;
|
|
14
18
|
issuedAt: number;
|
|
15
19
|
expiresAt: number;
|
|
16
20
|
kind?: string;
|
|
17
|
-
claims?: TClaims;
|
|
18
21
|
metadata?: {
|
|
19
22
|
ip?: string;
|
|
20
23
|
userAgent?: string;
|
|
@@ -23,25 +26,27 @@ interface AuthCredentialRow<TClaims extends object = object> {
|
|
|
23
26
|
};
|
|
24
27
|
parentCredentialId?: string;
|
|
25
28
|
rotatedAt?: number;
|
|
26
|
-
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
lastSeenAt?: number;
|
|
31
|
+
} & TPayload;
|
|
27
32
|
/**
|
|
28
33
|
* Structural surface of `AtscriptDbTable` covering exactly the methods this
|
|
29
34
|
* adapter calls. Kept loose to avoid pulling `@atscript/db` types into the
|
|
30
35
|
* `@aooth/auth` public surface — consumers pass `db.getTable(AoothAuthCredential)`
|
|
31
36
|
* directly and TypeScript matches by-shape.
|
|
32
37
|
*/
|
|
33
|
-
interface AuthCredentialTable<
|
|
34
|
-
insertOne(row: AuthCredentialRow<
|
|
38
|
+
interface AuthCredentialTable<TPayload extends object = object> {
|
|
39
|
+
insertOne(row: AuthCredentialRow<TPayload>): Promise<{
|
|
35
40
|
insertedId: unknown;
|
|
36
41
|
}>;
|
|
37
42
|
findOne(query: {
|
|
38
43
|
filter: Record<string, unknown>;
|
|
39
|
-
}): Promise<AuthCredentialRow<
|
|
44
|
+
}): Promise<AuthCredentialRow<TPayload> | null>;
|
|
40
45
|
findMany(query: {
|
|
41
46
|
filter?: Record<string, unknown>;
|
|
42
47
|
controls?: Record<string, unknown>;
|
|
43
|
-
}): Promise<AuthCredentialRow<
|
|
44
|
-
replaceOne(row: AuthCredentialRow<
|
|
48
|
+
}): Promise<AuthCredentialRow<TPayload>[]>;
|
|
49
|
+
replaceOne(row: AuthCredentialRow<TPayload>): Promise<{
|
|
45
50
|
matchedCount: number;
|
|
46
51
|
modifiedCount: number;
|
|
47
52
|
}>;
|
|
@@ -52,8 +57,8 @@ interface AuthCredentialTable<TClaims extends object = object> {
|
|
|
52
57
|
deletedCount: number;
|
|
53
58
|
}>;
|
|
54
59
|
}
|
|
55
|
-
interface CredentialStoreAtscriptDbOptions<
|
|
56
|
-
table: AuthCredentialTable<
|
|
60
|
+
interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
|
|
61
|
+
table: AuthCredentialTable<TPayload>;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* atscript-db-backed `CredentialStore`.
|
|
@@ -68,16 +73,17 @@ interface CredentialStoreAtscriptDbOptions<TClaims extends object> {
|
|
|
68
73
|
* `.as` model). Custom tables must keep `token` as PK or override the
|
|
69
74
|
* adapter.
|
|
70
75
|
*/
|
|
71
|
-
declare class CredentialStoreAtscriptDb<
|
|
76
|
+
declare class CredentialStoreAtscriptDb<TPayload extends object = object> implements CredentialStore<TPayload> {
|
|
72
77
|
private readonly table;
|
|
73
|
-
constructor(opts: CredentialStoreAtscriptDbOptions<
|
|
74
|
-
persist(state: CredentialState
|
|
75
|
-
retrieve(token: string): Promise<CredentialState
|
|
76
|
-
consume(token: string): Promise<CredentialState
|
|
77
|
-
update(token: string, state: CredentialState
|
|
78
|
+
constructor(opts: CredentialStoreAtscriptDbOptions<TPayload>);
|
|
79
|
+
persist(state: CredentialState & TPayload, ttl?: number): Promise<string>;
|
|
80
|
+
retrieve(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
81
|
+
consume(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
82
|
+
update(token: string, state: CredentialState & TPayload): Promise<string>;
|
|
78
83
|
revoke(token: string): Promise<void>;
|
|
84
|
+
touch(token: string, at: number): Promise<void>;
|
|
79
85
|
revokeAllForUser(userId: string): Promise<number>;
|
|
80
|
-
listForUser(userId: string): Promise<Array<CredentialState
|
|
86
|
+
listForUser(userId: string): Promise<Array<CredentialState & TPayload & {
|
|
81
87
|
token: string;
|
|
82
88
|
}>>;
|
|
83
89
|
}
|
package/dist/atscript-db.d.mts
CHANGED
|
@@ -1,20 +1,23 @@
|
|
|
1
|
-
import { a as CredentialState, t as CredentialStore } from "./store-
|
|
1
|
+
import { a as CredentialState, t as CredentialStore } from "./store-BG6m6oSJ.mjs";
|
|
2
2
|
|
|
3
3
|
//#region src/atscript-db/index.d.ts
|
|
4
4
|
/**
|
|
5
5
|
* Persisted row shape — mirrors `AoothAuthCredential` from
|
|
6
|
-
* `./auth-credential.as`. Re-declared here as a plain TS
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* `./auth-credential.as`. Re-declared here as a plain TS type so consumers can
|
|
7
|
+
* use the adapter without running the atscript build (and so `@aooth/auth`
|
|
8
|
+
* doesn't need to depend on `@atscript/typescript` at build time). When you DO
|
|
9
|
+
* wire the `.as` model, the shapes match by construction.
|
|
10
|
+
*
|
|
11
|
+
* The consumer's typed payload `TPayload` (the root fields they add to their
|
|
12
|
+
* `extends AoothAuthCredential` model) is intersected flat — those become real
|
|
13
|
+
* typed columns, replacing the dropped free-form `claims` blob.
|
|
10
14
|
*/
|
|
11
|
-
|
|
15
|
+
type AuthCredentialRow<TPayload extends object = object> = {
|
|
12
16
|
token: string;
|
|
13
17
|
userId: string;
|
|
14
18
|
issuedAt: number;
|
|
15
19
|
expiresAt: number;
|
|
16
20
|
kind?: string;
|
|
17
|
-
claims?: TClaims;
|
|
18
21
|
metadata?: {
|
|
19
22
|
ip?: string;
|
|
20
23
|
userAgent?: string;
|
|
@@ -23,25 +26,27 @@ interface AuthCredentialRow<TClaims extends object = object> {
|
|
|
23
26
|
};
|
|
24
27
|
parentCredentialId?: string;
|
|
25
28
|
rotatedAt?: number;
|
|
26
|
-
|
|
29
|
+
sessionId?: string;
|
|
30
|
+
lastSeenAt?: number;
|
|
31
|
+
} & TPayload;
|
|
27
32
|
/**
|
|
28
33
|
* Structural surface of `AtscriptDbTable` covering exactly the methods this
|
|
29
34
|
* adapter calls. Kept loose to avoid pulling `@atscript/db` types into the
|
|
30
35
|
* `@aooth/auth` public surface — consumers pass `db.getTable(AoothAuthCredential)`
|
|
31
36
|
* directly and TypeScript matches by-shape.
|
|
32
37
|
*/
|
|
33
|
-
interface AuthCredentialTable<
|
|
34
|
-
insertOne(row: AuthCredentialRow<
|
|
38
|
+
interface AuthCredentialTable<TPayload extends object = object> {
|
|
39
|
+
insertOne(row: AuthCredentialRow<TPayload>): Promise<{
|
|
35
40
|
insertedId: unknown;
|
|
36
41
|
}>;
|
|
37
42
|
findOne(query: {
|
|
38
43
|
filter: Record<string, unknown>;
|
|
39
|
-
}): Promise<AuthCredentialRow<
|
|
44
|
+
}): Promise<AuthCredentialRow<TPayload> | null>;
|
|
40
45
|
findMany(query: {
|
|
41
46
|
filter?: Record<string, unknown>;
|
|
42
47
|
controls?: Record<string, unknown>;
|
|
43
|
-
}): Promise<AuthCredentialRow<
|
|
44
|
-
replaceOne(row: AuthCredentialRow<
|
|
48
|
+
}): Promise<AuthCredentialRow<TPayload>[]>;
|
|
49
|
+
replaceOne(row: AuthCredentialRow<TPayload>): Promise<{
|
|
45
50
|
matchedCount: number;
|
|
46
51
|
modifiedCount: number;
|
|
47
52
|
}>;
|
|
@@ -52,8 +57,8 @@ interface AuthCredentialTable<TClaims extends object = object> {
|
|
|
52
57
|
deletedCount: number;
|
|
53
58
|
}>;
|
|
54
59
|
}
|
|
55
|
-
interface CredentialStoreAtscriptDbOptions<
|
|
56
|
-
table: AuthCredentialTable<
|
|
60
|
+
interface CredentialStoreAtscriptDbOptions<TPayload extends object> {
|
|
61
|
+
table: AuthCredentialTable<TPayload>;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* atscript-db-backed `CredentialStore`.
|
|
@@ -68,16 +73,17 @@ interface CredentialStoreAtscriptDbOptions<TClaims extends object> {
|
|
|
68
73
|
* `.as` model). Custom tables must keep `token` as PK or override the
|
|
69
74
|
* adapter.
|
|
70
75
|
*/
|
|
71
|
-
declare class CredentialStoreAtscriptDb<
|
|
76
|
+
declare class CredentialStoreAtscriptDb<TPayload extends object = object> implements CredentialStore<TPayload> {
|
|
72
77
|
private readonly table;
|
|
73
|
-
constructor(opts: CredentialStoreAtscriptDbOptions<
|
|
74
|
-
persist(state: CredentialState
|
|
75
|
-
retrieve(token: string): Promise<CredentialState
|
|
76
|
-
consume(token: string): Promise<CredentialState
|
|
77
|
-
update(token: string, state: CredentialState
|
|
78
|
+
constructor(opts: CredentialStoreAtscriptDbOptions<TPayload>);
|
|
79
|
+
persist(state: CredentialState & TPayload, ttl?: number): Promise<string>;
|
|
80
|
+
retrieve(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
81
|
+
consume(token: string): Promise<(CredentialState & TPayload) | null>;
|
|
82
|
+
update(token: string, state: CredentialState & TPayload): Promise<string>;
|
|
78
83
|
revoke(token: string): Promise<void>;
|
|
84
|
+
touch(token: string, at: number): Promise<void>;
|
|
79
85
|
revokeAllForUser(userId: string): Promise<number>;
|
|
80
|
-
listForUser(userId: string): Promise<Array<CredentialState
|
|
86
|
+
listForUser(userId: string): Promise<Array<CredentialState & TPayload & {
|
|
81
87
|
token: string;
|
|
82
88
|
}>>;
|
|
83
89
|
}
|
package/dist/atscript-db.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t as credentialPayloadOf } from "./payload-D-DzH5-J.mjs";
|
|
1
2
|
import { randomUUID } from "node:crypto";
|
|
2
3
|
//#region src/atscript-db/index.ts
|
|
3
4
|
/**
|
|
@@ -21,18 +22,7 @@ var CredentialStoreAtscriptDb = class {
|
|
|
21
22
|
async persist(state, ttl) {
|
|
22
23
|
const token = randomUUID();
|
|
23
24
|
const expiresAt = typeof ttl === "number" ? Date.now() + ttl : state.expiresAt;
|
|
24
|
-
|
|
25
|
-
token,
|
|
26
|
-
userId: state.userId,
|
|
27
|
-
issuedAt: state.issuedAt,
|
|
28
|
-
expiresAt,
|
|
29
|
-
kind: state.kind,
|
|
30
|
-
claims: state.claims,
|
|
31
|
-
metadata: state.metadata,
|
|
32
|
-
parentCredentialId: state.parentCredentialId,
|
|
33
|
-
rotatedAt: state.rotatedAt
|
|
34
|
-
};
|
|
35
|
-
await this.table.insertOne(row);
|
|
25
|
+
await this.table.insertOne(stateToRow(state, token, expiresAt));
|
|
36
26
|
return token;
|
|
37
27
|
}
|
|
38
28
|
async retrieve(token) {
|
|
@@ -56,23 +46,21 @@ var CredentialStoreAtscriptDb = class {
|
|
|
56
46
|
await this.revoke(token);
|
|
57
47
|
return token;
|
|
58
48
|
}
|
|
59
|
-
|
|
60
|
-
token,
|
|
61
|
-
userId: state.userId,
|
|
62
|
-
issuedAt: state.issuedAt,
|
|
63
|
-
expiresAt: state.expiresAt,
|
|
64
|
-
kind: state.kind,
|
|
65
|
-
claims: state.claims,
|
|
66
|
-
metadata: state.metadata,
|
|
67
|
-
parentCredentialId: state.parentCredentialId,
|
|
68
|
-
rotatedAt: state.rotatedAt
|
|
69
|
-
};
|
|
70
|
-
await this.table.replaceOne(row);
|
|
49
|
+
await this.table.replaceOne(stateToRow(state, token, state.expiresAt));
|
|
71
50
|
return token;
|
|
72
51
|
}
|
|
73
52
|
async revoke(token) {
|
|
74
53
|
await this.table.deleteOne(token);
|
|
75
54
|
}
|
|
55
|
+
async touch(token, at) {
|
|
56
|
+
const row = await this.table.findOne({ filter: { token } });
|
|
57
|
+
if (!row) return;
|
|
58
|
+
if (row.expiresAt <= Date.now()) return;
|
|
59
|
+
await this.table.replaceOne({
|
|
60
|
+
...row,
|
|
61
|
+
lastSeenAt: at
|
|
62
|
+
});
|
|
63
|
+
}
|
|
76
64
|
async revokeAllForUser(userId) {
|
|
77
65
|
return (await this.table.deleteMany({ userId })).deletedCount;
|
|
78
66
|
}
|
|
@@ -95,17 +83,40 @@ var CredentialStoreAtscriptDb = class {
|
|
|
95
83
|
return out;
|
|
96
84
|
}
|
|
97
85
|
};
|
|
86
|
+
/**
|
|
87
|
+
* Build the persisted row from a credential state: typed payload columns first
|
|
88
|
+
* (so an envelope field wins a name clash), then the fixed envelope fields and
|
|
89
|
+
* the resolved `token` + `expiresAt`. Shared by `persist` and `update`, which
|
|
90
|
+
* differ only in the `expiresAt` they resolve.
|
|
91
|
+
*/
|
|
92
|
+
function stateToRow(state, token, expiresAt) {
|
|
93
|
+
return {
|
|
94
|
+
...credentialPayloadOf(state),
|
|
95
|
+
token,
|
|
96
|
+
userId: state.userId,
|
|
97
|
+
issuedAt: state.issuedAt,
|
|
98
|
+
expiresAt,
|
|
99
|
+
kind: state.kind,
|
|
100
|
+
metadata: state.metadata,
|
|
101
|
+
parentCredentialId: state.parentCredentialId,
|
|
102
|
+
rotatedAt: state.rotatedAt,
|
|
103
|
+
sessionId: state.sessionId,
|
|
104
|
+
lastSeenAt: state.lastSeenAt
|
|
105
|
+
};
|
|
106
|
+
}
|
|
98
107
|
function rowToState(row) {
|
|
99
108
|
const state = {
|
|
109
|
+
...credentialPayloadOf(row),
|
|
100
110
|
userId: row.userId,
|
|
101
111
|
issuedAt: row.issuedAt,
|
|
102
112
|
expiresAt: row.expiresAt
|
|
103
113
|
};
|
|
104
|
-
if (row.claims !== void 0) state.claims = row.claims;
|
|
105
114
|
if (row.metadata !== void 0) state.metadata = row.metadata;
|
|
106
115
|
if (row.kind === "access" || row.kind === "refresh") state.kind = row.kind;
|
|
107
116
|
if (row.parentCredentialId !== void 0) state.parentCredentialId = row.parentCredentialId;
|
|
108
117
|
if (row.rotatedAt !== void 0) state.rotatedAt = row.rotatedAt;
|
|
118
|
+
if (row.sessionId !== void 0) state.sessionId = row.sessionId;
|
|
119
|
+
if (row.lastSeenAt !== void 0) state.lastSeenAt = row.lastSeenAt;
|
|
109
120
|
return state;
|
|
110
121
|
}
|
|
111
122
|
//#endregion
|
package/dist/authz.cjs
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_clock = require("./clock-Bl-H3eqE.cjs");
|
|
3
|
+
let node_crypto = require("node:crypto");
|
|
4
|
+
//#region src/authz/authz-errors.ts
|
|
5
|
+
/** A typed authorization-server failure. */
|
|
6
|
+
var AuthorizeError = class extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = "AuthorizeError";
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
//#endregion
|
|
15
|
+
//#region src/authz/client-policy.ts
|
|
16
|
+
/**
|
|
17
|
+
* `true` when `uri` is a syntactically valid http(s) URL whose host is a
|
|
18
|
+
* **loopback literal** — `127.0.0.1`, `::1`, or `localhost` — on any port (RFC
|
|
19
|
+
* 8252 §7.3). Rejects everything else, including the classic bypasses: a
|
|
20
|
+
* host-suffix (`127.0.0.1.evil.com`, `localhost.evil.com`), embedded credentials
|
|
21
|
+
* (`http://127.0.0.1@evil.com` → host `evil.com`), a non-http scheme, and a bare
|
|
22
|
+
* `0.0.0.0`. Only a local process can receive a loopback redirect, which is why
|
|
23
|
+
* an arbitrary port is safe — the binding is the loopback host + PKCE.
|
|
24
|
+
*/
|
|
25
|
+
function isLoopbackRedirectUri(uri) {
|
|
26
|
+
let url;
|
|
27
|
+
try {
|
|
28
|
+
url = new URL(uri);
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return false;
|
|
33
|
+
if (url.username !== "" || url.password !== "") return false;
|
|
34
|
+
const host = url.hostname.replace(/^\[|\]$/g, "");
|
|
35
|
+
return host === "127.0.0.1" || host === "::1" || host === "localhost";
|
|
36
|
+
}
|
|
37
|
+
const DEFAULT_CLI_TOKEN_POLICY = {
|
|
38
|
+
kind: "cli-session",
|
|
39
|
+
ttl: 720 * 60 * 6e4
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Tier-1 policy: accept any **loopback** `redirect_uri`, treat the client as a
|
|
43
|
+
* public client (no `client_id` / secret — PKCE is the binding), and mint the
|
|
44
|
+
* configured CLI token policy. Rejects every non-loopback redirect.
|
|
45
|
+
*/
|
|
46
|
+
var LoopbackClientPolicy = class {
|
|
47
|
+
tokenPolicy;
|
|
48
|
+
constructor(opts) {
|
|
49
|
+
this.tokenPolicy = opts?.tokenPolicy ?? DEFAULT_CLI_TOKEN_POLICY;
|
|
50
|
+
}
|
|
51
|
+
resolveClient(args) {
|
|
52
|
+
if (!isLoopbackRedirectUri(args.redirectUri)) throw new AuthorizeError("invalid_redirect", "redirect_uri must be a loopback address");
|
|
53
|
+
return {
|
|
54
|
+
redirectUri: args.redirectUri,
|
|
55
|
+
tokenPolicy: structuredClone(this.tokenPolicy)
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/authz/pending-authorization-store.ts
|
|
61
|
+
/**
|
|
62
|
+
* Storage seam for in-flight authorizations (AUTH-SERVER.md §4.3). Short-lived
|
|
63
|
+
* (≈ the login-session ceiling): created at `/authorize`, read+deleted at the
|
|
64
|
+
* wf terminal. An in-memory impl ships for single-process apps + tests; a
|
|
65
|
+
* multi-pod deployment provides a durable (e.g. Redis) impl under the same
|
|
66
|
+
* `PENDING_AUTHORIZATION_STORE_TOKEN` (from `@aooth/auth-moost`).
|
|
67
|
+
*/
|
|
68
|
+
var PendingAuthorizationStore = class {};
|
|
69
|
+
const DEFAULT_PENDING_TTL_MS = 15 * 6e4;
|
|
70
|
+
/**
|
|
71
|
+
* In-memory {@link PendingAuthorizationStore} — the reference impl for a
|
|
72
|
+
* single-process app + tests. `structuredClone` on read/write isolates callers.
|
|
73
|
+
*/
|
|
74
|
+
var PendingAuthorizationStoreMemory = class extends PendingAuthorizationStore {
|
|
75
|
+
store = /* @__PURE__ */ new Map();
|
|
76
|
+
clock;
|
|
77
|
+
ttlMs;
|
|
78
|
+
constructor(opts) {
|
|
79
|
+
super();
|
|
80
|
+
this.clock = opts?.clock ?? require_clock.defaultClock;
|
|
81
|
+
this.ttlMs = opts?.ttlMs ?? DEFAULT_PENDING_TTL_MS;
|
|
82
|
+
}
|
|
83
|
+
async create(rec) {
|
|
84
|
+
const now = this.clock.now();
|
|
85
|
+
const row = {
|
|
86
|
+
handle: (0, node_crypto.randomUUID)(),
|
|
87
|
+
redirectUri: rec.redirectUri,
|
|
88
|
+
codeChallenge: rec.codeChallenge,
|
|
89
|
+
tokenPolicy: structuredClone(rec.tokenPolicy),
|
|
90
|
+
createdAt: now,
|
|
91
|
+
expiresAt: now + this.ttlMs,
|
|
92
|
+
...rec.clientId !== void 0 && { clientId: rec.clientId },
|
|
93
|
+
...rec.clientState !== void 0 && { clientState: rec.clientState },
|
|
94
|
+
...rec.scope !== void 0 && { scope: rec.scope }
|
|
95
|
+
};
|
|
96
|
+
this.store.set(row.handle, structuredClone(row));
|
|
97
|
+
return { handle: row.handle };
|
|
98
|
+
}
|
|
99
|
+
async get(handle) {
|
|
100
|
+
const row = this.store.get(handle);
|
|
101
|
+
if (!row) return null;
|
|
102
|
+
if (row.expiresAt <= this.clock.now()) {
|
|
103
|
+
this.store.delete(handle);
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
return structuredClone(row);
|
|
107
|
+
}
|
|
108
|
+
async delete(handle) {
|
|
109
|
+
return this.store.delete(handle);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region src/authz/auth-code-store.ts
|
|
114
|
+
/**
|
|
115
|
+
* Storage seam for issued authorization codes (AUTH-SERVER.md §4.3). Very
|
|
116
|
+
* short-lived (≈ 30–60 s) and **single-use**: {@link consume} returns the row
|
|
117
|
+
* AND invalidates it in one atomic step, so a concurrent double-redeem (or a
|
|
118
|
+
* back-button replay) yields the code to exactly one caller. An in-memory impl
|
|
119
|
+
* ships (atomic for free in single-threaded JS); a durable impl must implement
|
|
120
|
+
* `consume` as an atomic check-and-delete (e.g. `withCas` / `@db.column.version`,
|
|
121
|
+
* or a Redis `GETDEL`).
|
|
122
|
+
*/
|
|
123
|
+
var AuthCodeStore = class {};
|
|
124
|
+
const DEFAULT_CODE_TTL_MS = 6e4;
|
|
125
|
+
/**
|
|
126
|
+
* In-memory {@link AuthCodeStore} — the reference impl. `consume` is atomic
|
|
127
|
+
* because the `get` + `delete` run with no intervening `await`, so a second
|
|
128
|
+
* concurrent `consume` of the same code always misses.
|
|
129
|
+
*/
|
|
130
|
+
var AuthCodeStoreMemory = class extends AuthCodeStore {
|
|
131
|
+
store = /* @__PURE__ */ new Map();
|
|
132
|
+
clock;
|
|
133
|
+
ttlMs;
|
|
134
|
+
constructor(opts) {
|
|
135
|
+
super();
|
|
136
|
+
this.clock = opts?.clock ?? require_clock.defaultClock;
|
|
137
|
+
this.ttlMs = opts?.ttlMs ?? DEFAULT_CODE_TTL_MS;
|
|
138
|
+
}
|
|
139
|
+
async mint(rec) {
|
|
140
|
+
const code = (0, node_crypto.randomUUID)();
|
|
141
|
+
const row = {
|
|
142
|
+
code,
|
|
143
|
+
userId: rec.userId,
|
|
144
|
+
codeChallenge: rec.codeChallenge,
|
|
145
|
+
redirectUri: rec.redirectUri,
|
|
146
|
+
tokenPolicy: structuredClone(rec.tokenPolicy),
|
|
147
|
+
expiresAt: this.clock.now() + this.ttlMs,
|
|
148
|
+
...rec.clientId !== void 0 && { clientId: rec.clientId }
|
|
149
|
+
};
|
|
150
|
+
this.store.set(code, structuredClone(row));
|
|
151
|
+
return { code };
|
|
152
|
+
}
|
|
153
|
+
async consume(code) {
|
|
154
|
+
const row = this.store.get(code);
|
|
155
|
+
if (!row) return null;
|
|
156
|
+
this.store.delete(code);
|
|
157
|
+
if (row.expiresAt <= this.clock.now()) return null;
|
|
158
|
+
return structuredClone(row);
|
|
159
|
+
}
|
|
160
|
+
};
|
|
161
|
+
//#endregion
|
|
162
|
+
exports.AuthCodeStore = AuthCodeStore;
|
|
163
|
+
exports.AuthCodeStoreMemory = AuthCodeStoreMemory;
|
|
164
|
+
exports.AuthorizeError = AuthorizeError;
|
|
165
|
+
exports.LoopbackClientPolicy = LoopbackClientPolicy;
|
|
166
|
+
exports.PendingAuthorizationStore = PendingAuthorizationStore;
|
|
167
|
+
exports.PendingAuthorizationStoreMemory = PendingAuthorizationStoreMemory;
|
|
168
|
+
exports.isLoopbackRedirectUri = isLoopbackRedirectUri;
|