@fragno-dev/api-fragment 0.0.0-stage → 0.0.1
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/LICENSE.md +16 -0
- package/README.md +249 -2
- package/dist/browser/client/client-3ZV9sw3i.js +2580 -0
- package/dist/browser/client/client-3ZV9sw3i.js.map +1 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts +711 -0
- package/dist/browser/client/client-DPjmoaJQ.d.ts.map +1 -0
- package/dist/browser/client/client.d.ts +1645 -0
- package/dist/browser/client/client.d.ts.map +1 -0
- package/dist/browser/client/client.js +2 -0
- package/dist/browser/client/react.d.ts +1665 -0
- package/dist/browser/client/react.d.ts.map +1 -0
- package/dist/browser/client/react.js +194 -0
- package/dist/browser/client/react.js.map +1 -0
- package/dist/browser/client/solid.d.ts +1668 -0
- package/dist/browser/client/solid.d.ts.map +1 -0
- package/dist/browser/client/solid.js +129 -0
- package/dist/browser/client/solid.js.map +1 -0
- package/dist/browser/client/svelte.d.ts +1668 -0
- package/dist/browser/client/svelte.d.ts.map +1 -0
- package/dist/browser/client/svelte.js +131 -0
- package/dist/browser/client/svelte.js.map +1 -0
- package/dist/browser/client/vanilla.d.ts +1695 -0
- package/dist/browser/client/vanilla.d.ts.map +1 -0
- package/dist/browser/client/vanilla.js +159 -0
- package/dist/browser/client/vanilla.js.map +1 -0
- package/dist/browser/client/vue.d.ts +1668 -0
- package/dist/browser/client/vue.d.ts.map +1 -0
- package/dist/browser/client/vue.js +127 -0
- package/dist/browser/client/vue.js.map +1 -0
- package/dist/browser/client-D4NnY-zV.d.ts +713 -0
- package/dist/browser/client-D4NnY-zV.d.ts.map +1 -0
- package/dist/browser/client-DQE0c7GI.d.ts +711 -0
- package/dist/browser/client-DQE0c7GI.d.ts.map +1 -0
- package/dist/browser/index.d.ts +3130 -0
- package/dist/browser/index.d.ts.map +1 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/src-BrYPVbAN.js +2404 -0
- package/dist/browser/src-BrYPVbAN.js.map +1 -0
- package/dist/browser/src-t-4wp9-f.js +2400 -0
- package/dist/browser/src-t-4wp9-f.js.map +1 -0
- package/dist/node/api-types.d.ts +731 -0
- package/dist/node/api-types.d.ts.map +1 -0
- package/dist/node/api-types.js +281 -0
- package/dist/node/api-types.js.map +1 -0
- package/dist/node/crypto.d.ts +6 -0
- package/dist/node/crypto.d.ts.map +1 -0
- package/dist/node/crypto.js +103 -0
- package/dist/node/crypto.js.map +1 -0
- package/dist/node/definition.d.ts +638 -0
- package/dist/node/definition.d.ts.map +1 -0
- package/dist/node/definition.js +315 -0
- package/dist/node/definition.js.map +1 -0
- package/dist/node/oauth.js +124 -0
- package/dist/node/oauth.js.map +1 -0
- package/dist/node/routes.d.ts +1585 -0
- package/dist/node/routes.d.ts.map +1 -0
- package/dist/node/routes.js +1100 -0
- package/dist/node/routes.js.map +1 -0
- package/dist/node/schema.d.ts +99 -0
- package/dist/node/schema.d.ts.map +1 -0
- package/dist/node/schema.js +16 -0
- package/dist/node/schema.js.map +1 -0
- package/dist/node/server.d.ts +1585 -0
- package/dist/node/server.d.ts.map +1 -0
- package/dist/node/server.js +12 -0
- package/dist/node/server.js.map +1 -0
- package/dist/node/services.d.ts +10 -0
- package/dist/node/services.d.ts.map +1 -0
- package/dist/node/services.js +304 -0
- package/dist/node/services.js.map +1 -0
- package/dist/node/webhooks/auth.d.ts +133 -0
- package/dist/node/webhooks/auth.d.ts.map +1 -0
- package/dist/node/webhooks/auth.js +268 -0
- package/dist/node/webhooks/auth.js.map +1 -0
- package/dist/node/webhooks/verification.d.ts +102 -0
- package/dist/node/webhooks/verification.d.ts.map +1 -0
- package/dist/node/webhooks/verification.js +119 -0
- package/dist/node/webhooks/verification.js.map +1 -0
- package/package.json +136 -3
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import { apiSchema } from "./schema.js";
|
|
2
|
+
import { apiAuthReadyUntil, createOAuthCallbackSnapshot, createOAuthStartSnapshot, isApiAuthReady, performApiRequest, resolveApiOperationAuth, storedAuthPayloadSchema } from "./services.js";
|
|
3
|
+
import { defineFragment } from "@fragno-dev/core";
|
|
4
|
+
import { withDatabase } from "@fragno-dev/db";
|
|
5
|
+
//#region src/definition.ts
|
|
6
|
+
/** Expiry changes readiness without a write, so a write that starts the countdown schedules it. */
|
|
7
|
+
function scheduleAuthExpiry(uow, connectionId, authMode, after) {
|
|
8
|
+
const readyUntil = apiAuthReadyUntil(authMode, after);
|
|
9
|
+
if (readyUntil && after.expiresAt && isApiAuthReady(authMode, after)) uow.triggerHook("onAuthExpired", {
|
|
10
|
+
connectionId,
|
|
11
|
+
expiresAt: after.expiresAt.toISOString()
|
|
12
|
+
}, { processAt: readyUntil });
|
|
13
|
+
}
|
|
14
|
+
function connectionHookSnapshot(connection) {
|
|
15
|
+
return {
|
|
16
|
+
slug: connection.id.toString(),
|
|
17
|
+
name: connection.name ?? null,
|
|
18
|
+
baseUrl: connection.baseUrl,
|
|
19
|
+
authMode: connection.authMode,
|
|
20
|
+
status: connection.status
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const apiFragmentDefinition = defineFragment("api-fragment").extend(withDatabase(apiSchema)).provideHooks(({ defineHook, config }) => ({
|
|
24
|
+
onConnectionChanged: defineHook(async function(payload) {
|
|
25
|
+
await config.onConnectionChanged?.(payload, this);
|
|
26
|
+
}),
|
|
27
|
+
onConnectionDeleted: defineHook(async function(payload) {
|
|
28
|
+
await config.onConnectionDeleted?.(payload, this);
|
|
29
|
+
}),
|
|
30
|
+
onConnectionReadinessChanged: defineHook(async function(payload) {
|
|
31
|
+
const connection = await this.handlerTx().retrieve(({ forSchema }) => forSchema(apiSchema).findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", payload.connectionId)))).transformRetrieve(([connection]) => connection && connectionHookSnapshot(connection)).execute();
|
|
32
|
+
await config.onConnectionReadinessChanged?.({
|
|
33
|
+
...payload,
|
|
34
|
+
connection
|
|
35
|
+
}, this);
|
|
36
|
+
}),
|
|
37
|
+
onAuthExpired: defineHook(async function({ connectionId, expiresAt }) {
|
|
38
|
+
const expired = await this.handlerTx().retrieve(({ forSchema }) => forSchema(apiSchema).findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", connectionId))).findFirst("secret", (b) => b.whereIndex("idx_secret_connection_kind", (eb) => eb.and(eb("connectionId", "=", connectionId), eb("kind", "=", "auth"))))).transformRetrieve(([connection, secret]) => connection !== null && secret !== null && secret.expiresAt?.getTime() === Date.parse(expiresAt) && apiAuthReadyUntil(connection.authMode, secret) !== null ? connectionHookSnapshot(connection) : null).execute();
|
|
39
|
+
if (expired) await config.onConnectionReadinessChanged?.({
|
|
40
|
+
connectionId,
|
|
41
|
+
ready: false,
|
|
42
|
+
connection: expired
|
|
43
|
+
}, this);
|
|
44
|
+
}),
|
|
45
|
+
onWebhookEndpointChanged: defineHook(async function(payload) {
|
|
46
|
+
await config.onWebhookEndpointChanged?.(payload, this);
|
|
47
|
+
}),
|
|
48
|
+
onWebhookReceived: defineHook(async function(payload) {
|
|
49
|
+
await config.onWebhookReceived?.({
|
|
50
|
+
endpointId: payload.endpointId,
|
|
51
|
+
deliveryId: payload.deliveryId,
|
|
52
|
+
hookId: payload.hookId,
|
|
53
|
+
receivedAt: payload.receivedAt,
|
|
54
|
+
headers: payload.headers,
|
|
55
|
+
query: payload.query,
|
|
56
|
+
body: payload.body,
|
|
57
|
+
contentType: payload.contentType
|
|
58
|
+
}, this);
|
|
59
|
+
})
|
|
60
|
+
})).providesBaseService(({ defineService, config }) => defineService({
|
|
61
|
+
startOAuth: function(input) {
|
|
62
|
+
return this.serviceTx(apiSchema).retrieve((uow) => uow.findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.connectionId))).findFirst("secret", (b) => b.whereIndex("idx_secret_connection_kind", (eb) => eb.and(eb("connectionId", "=", input.connectionId), eb("kind", "=", "auth"))))).transformRetrieve(async ([connection, secret]) => {
|
|
63
|
+
if (!connection) return {
|
|
64
|
+
found: false,
|
|
65
|
+
reason: "connection_not_found"
|
|
66
|
+
};
|
|
67
|
+
if (!secret) return {
|
|
68
|
+
found: false,
|
|
69
|
+
reason: "auth_not_configured"
|
|
70
|
+
};
|
|
71
|
+
const auth = storedAuthPayloadSchema.parse(JSON.parse(secret.payload));
|
|
72
|
+
if (auth.type !== "oauth") return {
|
|
73
|
+
found: false,
|
|
74
|
+
reason: "auth_not_oauth"
|
|
75
|
+
};
|
|
76
|
+
const snapshot = await createOAuthStartSnapshot({
|
|
77
|
+
connection,
|
|
78
|
+
auth,
|
|
79
|
+
stateId: input.stateId,
|
|
80
|
+
redirectUri: input.redirectUri,
|
|
81
|
+
scopes: input.scopes,
|
|
82
|
+
extraAuthorizationParams: input.extraAuthorizationParams
|
|
83
|
+
});
|
|
84
|
+
const clientPayload = JSON.stringify({
|
|
85
|
+
...auth,
|
|
86
|
+
tokens: void 0,
|
|
87
|
+
redirectUri: void 0
|
|
88
|
+
});
|
|
89
|
+
return {
|
|
90
|
+
found: true,
|
|
91
|
+
authorizationUrl: snapshot.authorizationUrl.toString(),
|
|
92
|
+
oauthState: snapshot.oauthState,
|
|
93
|
+
connectionId: connection.id.toString(),
|
|
94
|
+
secretId: secret.id,
|
|
95
|
+
clientPayload,
|
|
96
|
+
discardChangesReadiness: isApiAuthReady(connection.authMode, secret) !== isApiAuthReady(connection.authMode, {
|
|
97
|
+
payload: clientPayload,
|
|
98
|
+
expiresAt: null
|
|
99
|
+
})
|
|
100
|
+
};
|
|
101
|
+
}).mutate(({ uow, retrieveResult }) => {
|
|
102
|
+
if (!retrieveResult.found) return retrieveResult;
|
|
103
|
+
uow.create("oauthState", {
|
|
104
|
+
id: retrieveResult.oauthState.id,
|
|
105
|
+
connectionId: retrieveResult.oauthState.connectionId,
|
|
106
|
+
authorizationUrl: retrieveResult.oauthState.authorizationUrl,
|
|
107
|
+
codeVerifier: retrieveResult.oauthState.codeVerifier,
|
|
108
|
+
redirectUri: retrieveResult.oauthState.redirectUri,
|
|
109
|
+
scope: retrieveResult.oauthState.scope,
|
|
110
|
+
expiresAt: retrieveResult.oauthState.expiresAt,
|
|
111
|
+
consumedAt: null
|
|
112
|
+
});
|
|
113
|
+
if (input.discardTokens) {
|
|
114
|
+
uow.update("secret", retrieveResult.secretId, (b) => b.set({
|
|
115
|
+
payload: retrieveResult.clientPayload,
|
|
116
|
+
expiresAt: null,
|
|
117
|
+
updatedAt: b.now()
|
|
118
|
+
}).check());
|
|
119
|
+
if (retrieveResult.discardChangesReadiness) uow.triggerHook("onConnectionReadinessChanged", {
|
|
120
|
+
connectionId: retrieveResult.connectionId,
|
|
121
|
+
ready: false
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
found: true,
|
|
126
|
+
authorizationUrl: retrieveResult.authorizationUrl
|
|
127
|
+
};
|
|
128
|
+
}).transform(({ retrieveResult }) => retrieveResult).build();
|
|
129
|
+
},
|
|
130
|
+
completeOAuthCallback: function(input) {
|
|
131
|
+
const stateConnectionSeparatorIndex = input.stateId.lastIndexOf(":");
|
|
132
|
+
const connectionId = stateConnectionSeparatorIndex === -1 ? "" : input.stateId.slice(0, stateConnectionSeparatorIndex);
|
|
133
|
+
return this.serviceTx(apiSchema).retrieve((uow) => uow.findFirst("oauthState", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.stateId))).findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", connectionId))).findFirst("secret", (b) => b.whereIndex("idx_secret_connection_kind", (eb) => eb.and(eb("connectionId", "=", connectionId), eb("kind", "=", "auth"))))).transformRetrieve(async ([state, connection, secret]) => {
|
|
134
|
+
if (!state || state.consumedAt || new Date(state.expiresAt).getTime() < Date.now()) return {
|
|
135
|
+
found: false,
|
|
136
|
+
reason: "invalid_state"
|
|
137
|
+
};
|
|
138
|
+
if (!connection) return {
|
|
139
|
+
found: false,
|
|
140
|
+
reason: "connection_not_found"
|
|
141
|
+
};
|
|
142
|
+
if (!secret) return {
|
|
143
|
+
found: false,
|
|
144
|
+
reason: "auth_not_configured"
|
|
145
|
+
};
|
|
146
|
+
const auth = storedAuthPayloadSchema.parse(JSON.parse(secret.payload));
|
|
147
|
+
if (auth.type !== "oauth") return {
|
|
148
|
+
found: false,
|
|
149
|
+
reason: "auth_not_oauth"
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
found: true,
|
|
153
|
+
connection,
|
|
154
|
+
changes: await createOAuthCallbackSnapshot({
|
|
155
|
+
config,
|
|
156
|
+
stateId: input.stateId,
|
|
157
|
+
code: input.code,
|
|
158
|
+
state,
|
|
159
|
+
auth
|
|
160
|
+
}),
|
|
161
|
+
wasReady: isApiAuthReady(connection.authMode, secret)
|
|
162
|
+
};
|
|
163
|
+
}).mutate(({ uow, retrieveResult }) => {
|
|
164
|
+
if (!retrieveResult.found) return retrieveResult;
|
|
165
|
+
const connectionId = retrieveResult.connection.id.toString();
|
|
166
|
+
if (retrieveResult.changes.authPayload) {
|
|
167
|
+
uow.update("secret", `${connectionId}:auth`, (b) => b.set({
|
|
168
|
+
payload: retrieveResult.changes.authPayload,
|
|
169
|
+
expiresAt: retrieveResult.changes.authExpiresAt ?? null,
|
|
170
|
+
updatedAt: b.now()
|
|
171
|
+
}));
|
|
172
|
+
uow.update("api_connection", retrieveResult.connection.id, (b) => b.set({
|
|
173
|
+
authMode: "oauth",
|
|
174
|
+
updatedAt: b.now()
|
|
175
|
+
}).check());
|
|
176
|
+
const connection = connectionHookSnapshot({
|
|
177
|
+
...retrieveResult.connection,
|
|
178
|
+
authMode: "oauth"
|
|
179
|
+
});
|
|
180
|
+
uow.triggerHook("onConnectionChanged", {
|
|
181
|
+
connectionId,
|
|
182
|
+
connection
|
|
183
|
+
});
|
|
184
|
+
const after = {
|
|
185
|
+
payload: retrieveResult.changes.authPayload,
|
|
186
|
+
expiresAt: retrieveResult.changes.authExpiresAt ?? null
|
|
187
|
+
};
|
|
188
|
+
const ready = isApiAuthReady("oauth", after);
|
|
189
|
+
if (ready !== retrieveResult.wasReady) uow.triggerHook("onConnectionReadinessChanged", {
|
|
190
|
+
connectionId,
|
|
191
|
+
ready
|
|
192
|
+
});
|
|
193
|
+
scheduleAuthExpiry(uow, connectionId, "oauth", after);
|
|
194
|
+
}
|
|
195
|
+
uow.update("oauthState", input.stateId, (b) => b.set({ consumedAt: b.now() }));
|
|
196
|
+
return { found: true };
|
|
197
|
+
}).transform(({ retrieveResult }) => retrieveResult).build();
|
|
198
|
+
},
|
|
199
|
+
executeApiRequest: function(input) {
|
|
200
|
+
return this.serviceTx(apiSchema).retrieve((uow) => uow.findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.connectionId))).find("secret", (b) => b.whereIndex("idx_secret_connection_kind", (eb) => eb("connectionId", "=", input.connectionId)))).transformRetrieve(async ([connection, secrets]) => {
|
|
201
|
+
if (!connection) return { found: false };
|
|
202
|
+
if (connection.status !== "active") return {
|
|
203
|
+
found: false,
|
|
204
|
+
reason: "connection_disabled"
|
|
205
|
+
};
|
|
206
|
+
const resolved = await resolveApiOperationAuth({
|
|
207
|
+
config,
|
|
208
|
+
secrets
|
|
209
|
+
});
|
|
210
|
+
const response = await performApiRequest({
|
|
211
|
+
config,
|
|
212
|
+
baseUrl: connection.baseUrl,
|
|
213
|
+
authorization: resolved.authorization,
|
|
214
|
+
request: input.request
|
|
215
|
+
});
|
|
216
|
+
return {
|
|
217
|
+
found: true,
|
|
218
|
+
connection,
|
|
219
|
+
secrets,
|
|
220
|
+
authChanges: resolved.authChanges,
|
|
221
|
+
response
|
|
222
|
+
};
|
|
223
|
+
}).mutate(({ uow, retrieveResult }) => {
|
|
224
|
+
if (!retrieveResult.found) return retrieveResult;
|
|
225
|
+
const connectionId = retrieveResult.connection.id.toString();
|
|
226
|
+
const authChanges = retrieveResult.authChanges;
|
|
227
|
+
if (authChanges?.authPayload) {
|
|
228
|
+
const existing = retrieveResult.secrets.find((secret) => secret.kind === "auth");
|
|
229
|
+
if (existing) uow.update("secret", existing.id, (b) => b.set({
|
|
230
|
+
payload: authChanges.authPayload,
|
|
231
|
+
expiresAt: authChanges.authExpiresAt ?? null,
|
|
232
|
+
updatedAt: b.now()
|
|
233
|
+
}).check());
|
|
234
|
+
else uow.create("secret", {
|
|
235
|
+
id: `${connectionId}:auth`,
|
|
236
|
+
connectionId,
|
|
237
|
+
kind: "auth",
|
|
238
|
+
payload: authChanges.authPayload,
|
|
239
|
+
expiresAt: authChanges.authExpiresAt ?? null
|
|
240
|
+
});
|
|
241
|
+
uow.triggerHook("onConnectionChanged", {
|
|
242
|
+
connectionId,
|
|
243
|
+
connection: connectionHookSnapshot(retrieveResult.connection)
|
|
244
|
+
});
|
|
245
|
+
const { authMode } = retrieveResult.connection;
|
|
246
|
+
const after = {
|
|
247
|
+
payload: authChanges.authPayload,
|
|
248
|
+
expiresAt: authChanges.authExpiresAt ?? null
|
|
249
|
+
};
|
|
250
|
+
const ready = isApiAuthReady(authMode, after);
|
|
251
|
+
if (ready !== isApiAuthReady(authMode, existing)) uow.triggerHook("onConnectionReadinessChanged", {
|
|
252
|
+
connectionId,
|
|
253
|
+
ready
|
|
254
|
+
});
|
|
255
|
+
scheduleAuthExpiry(uow, connectionId, authMode, after);
|
|
256
|
+
}
|
|
257
|
+
return {
|
|
258
|
+
found: true,
|
|
259
|
+
response: retrieveResult.response
|
|
260
|
+
};
|
|
261
|
+
}).transform(({ mutateResult }) => mutateResult).build();
|
|
262
|
+
},
|
|
263
|
+
refreshClientCredentialsToken: function(input) {
|
|
264
|
+
return this.serviceTx(apiSchema).retrieve((uow) => uow.findFirst("api_connection", (b) => b.whereIndex("primary", (eb) => eb("id", "=", input.connectionId))).find("secret", (b) => b.whereIndex("idx_secret_connection_kind", (eb) => eb("connectionId", "=", input.connectionId)))).transformRetrieve(async ([connection, secrets]) => {
|
|
265
|
+
if (!connection) return { found: false };
|
|
266
|
+
const resolved = await resolveApiOperationAuth({
|
|
267
|
+
config,
|
|
268
|
+
secrets
|
|
269
|
+
});
|
|
270
|
+
return {
|
|
271
|
+
found: true,
|
|
272
|
+
connection,
|
|
273
|
+
secrets,
|
|
274
|
+
authChanges: resolved.authChanges,
|
|
275
|
+
tokenPresent: Boolean(resolved.authorization)
|
|
276
|
+
};
|
|
277
|
+
}).mutate(({ uow, retrieveResult }) => {
|
|
278
|
+
if (!retrieveResult.found) return retrieveResult;
|
|
279
|
+
const authChanges = retrieveResult.authChanges;
|
|
280
|
+
if (authChanges?.authPayload) {
|
|
281
|
+
const existing = retrieveResult.secrets.find((secret) => secret.kind === "auth");
|
|
282
|
+
if (existing) uow.update("secret", existing.id, (b) => b.set({
|
|
283
|
+
payload: authChanges.authPayload,
|
|
284
|
+
expiresAt: authChanges.authExpiresAt ?? null,
|
|
285
|
+
updatedAt: b.now()
|
|
286
|
+
}).check());
|
|
287
|
+
const connectionId = retrieveResult.connection.id.toString();
|
|
288
|
+
const connection = connectionHookSnapshot(retrieveResult.connection);
|
|
289
|
+
uow.triggerHook("onConnectionChanged", {
|
|
290
|
+
connectionId,
|
|
291
|
+
connection
|
|
292
|
+
});
|
|
293
|
+
const { authMode } = retrieveResult.connection;
|
|
294
|
+
const after = {
|
|
295
|
+
payload: authChanges.authPayload,
|
|
296
|
+
expiresAt: authChanges.authExpiresAt ?? null
|
|
297
|
+
};
|
|
298
|
+
const ready = isApiAuthReady(authMode, after);
|
|
299
|
+
if (ready !== isApiAuthReady(authMode, existing)) uow.triggerHook("onConnectionReadinessChanged", {
|
|
300
|
+
connectionId,
|
|
301
|
+
ready
|
|
302
|
+
});
|
|
303
|
+
scheduleAuthExpiry(uow, connectionId, authMode, after);
|
|
304
|
+
}
|
|
305
|
+
return {
|
|
306
|
+
found: true,
|
|
307
|
+
tokenPresent: retrieveResult.tokenPresent
|
|
308
|
+
};
|
|
309
|
+
}).transform(({ mutateResult }) => mutateResult).build();
|
|
310
|
+
}
|
|
311
|
+
})).build();
|
|
312
|
+
//#endregion
|
|
313
|
+
export { apiFragmentDefinition };
|
|
314
|
+
|
|
315
|
+
//# sourceMappingURL=definition.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition.js","names":[],"sources":["../../src/definition.ts"],"sourcesContent":["import { defineFragment } from \"@fragno-dev/core\";\nimport { withDatabase, type HookContext, type HookFn, type TypedUnitOfWork } from \"@fragno-dev/db\";\n\nimport type {\n ApiFragmentConfig as BaseApiFragmentConfig,\n ApiRequestInput,\n WebhookEndpoint,\n} from \"./api-types\";\nimport { apiSchema } from \"./schema\";\nimport {\n apiAuthReadyUntil,\n createOAuthCallbackSnapshot,\n createOAuthStartSnapshot,\n isApiAuthReady,\n performApiRequest,\n resolveApiOperationAuth,\n storedAuthPayloadSchema,\n} from \"./services\";\n\nexport interface ApiConnectionHookSnapshot {\n slug: string;\n name: string | null;\n baseUrl: string;\n authMode: string;\n status: string;\n}\n\nexport interface ApiConnectionChangedPayload {\n connectionId: string;\n connection: ApiConnectionHookSnapshot;\n}\n\nexport interface ApiConnectionDeletedPayload {\n connectionId: string;\n previous: ApiConnectionHookSnapshot;\n}\n\nexport interface ApiConnectionReadinessChangedPayload {\n connectionId: string;\n /** Whether stored auth can authorize requests; providers can still reject it. */\n ready: boolean;\n}\n\n/**\n * A readiness change as delivered. The connection is read at delivery rather than stored with the\n * hook, so it is the current connection, or null after it was deleted.\n */\nexport interface ApiConnectionReadinessChange extends ApiConnectionReadinessChangedPayload {\n connection: ApiConnectionHookSnapshot | null;\n}\n\n/** The token stored when expiry was scheduled; a later write replaces it and reports itself. */\ninterface InternalAuthExpiredPayload {\n connectionId: string;\n expiresAt: string;\n}\n\ninterface InternalWebhookReceivedPayload {\n endpointId: string;\n deliveryId: string;\n hookId: string;\n receivedAt: string;\n headers: Record<string, string>;\n query: Record<string, string>;\n rawBody: string;\n body: Record<string, unknown>;\n contentType: string | null;\n}\n\nexport interface WebhookEndpointHookSnapshot {\n name: WebhookEndpoint[\"name\"];\n status: WebhookEndpoint[\"status\"];\n authConfig: WebhookEndpoint[\"authConfig\"];\n verification: WebhookEndpoint[\"verification\"];\n deliveryIdentity: WebhookEndpoint[\"deliveryIdentity\"];\n secretRefs: WebhookEndpoint[\"secretRefs\"];\n}\n\nexport type WebhookEndpointChangedPayload =\n | { change: \"created\"; endpointId: string; endpoint: WebhookEndpointHookSnapshot }\n | {\n change: \"updated\";\n endpointId: string;\n endpoint: WebhookEndpointHookSnapshot;\n /** Lets consumers tell whether the write changed if the endpoint accepts deliveries. */\n previousStatus: WebhookEndpoint[\"status\"];\n }\n | { change: \"deleted\"; endpointId: string };\n\nexport interface WebhookReceivedPayload {\n endpointId: string;\n deliveryId: string;\n hookId: string;\n receivedAt: string;\n headers: Record<string, string>;\n query: Record<string, string>;\n body: Record<string, unknown>;\n contentType: string | null;\n}\n\nexport interface ApiFragmentHooksConfig {\n onConnectionChanged?: (\n payload: ApiConnectionChangedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n onConnectionDeleted?: (\n payload: ApiConnectionDeletedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n /**\n * Fires when a write changes whether the connection can authorize requests, including creation,\n * and when an access token without a refresh token expires.\n */\n onConnectionReadinessChanged?: (\n payload: ApiConnectionReadinessChange,\n context: HookContext,\n ) => Promise<void> | void;\n onWebhookEndpointChanged?: (\n payload: WebhookEndpointChangedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n onWebhookReceived?: (\n payload: WebhookReceivedPayload,\n context: HookContext,\n ) => Promise<void> | void;\n}\n\nexport type ApiFragmentConfig = BaseApiFragmentConfig & ApiFragmentHooksConfig;\n\nexport type ApiHooksMap = {\n onConnectionChanged: HookFn<ApiConnectionChangedPayload>;\n onConnectionDeleted: HookFn<ApiConnectionDeletedPayload>;\n onConnectionReadinessChanged: HookFn<ApiConnectionReadinessChangedPayload>;\n onAuthExpired: HookFn<InternalAuthExpiredPayload>;\n onWebhookEndpointChanged: HookFn<WebhookEndpointChangedPayload>;\n onWebhookReceived: HookFn<InternalWebhookReceivedPayload>;\n};\n\ntype ApiUow = TypedUnitOfWork<typeof apiSchema, unknown[], unknown, ApiHooksMap>;\n\n/** Expiry changes readiness without a write, so a write that starts the countdown schedules it. */\nfunction scheduleAuthExpiry(\n uow: ApiUow,\n connectionId: string,\n authMode: string,\n after: { payload: string; expiresAt: Date | null },\n) {\n const readyUntil = apiAuthReadyUntil(authMode, after);\n if (readyUntil && after.expiresAt && isApiAuthReady(authMode, after)) {\n uow.triggerHook(\n \"onAuthExpired\",\n { connectionId, expiresAt: after.expiresAt.toISOString() },\n { processAt: readyUntil },\n );\n }\n}\n\nfunction connectionHookSnapshot(connection: {\n id: { toString(): string } | string;\n name?: string | null;\n baseUrl: string;\n authMode: string;\n status: string;\n}): ApiConnectionHookSnapshot {\n return {\n slug: connection.id.toString(),\n name: connection.name ?? null,\n baseUrl: connection.baseUrl,\n authMode: connection.authMode,\n status: connection.status,\n };\n}\n\nexport const apiFragmentDefinition = defineFragment<ApiFragmentConfig>(\"api-fragment\")\n .extend(withDatabase(apiSchema))\n .provideHooks<ApiHooksMap>(({ defineHook, config }) => ({\n onConnectionChanged: defineHook(async function (payload) {\n await config.onConnectionChanged?.(payload, this);\n }),\n onConnectionDeleted: defineHook(async function (payload) {\n await config.onConnectionDeleted?.(payload, this);\n }),\n onConnectionReadinessChanged: defineHook(async function (payload) {\n const connection = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(apiSchema).findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", payload.connectionId)),\n ),\n )\n .transformRetrieve(([connection]) => connection && connectionHookSnapshot(connection))\n .execute();\n await config.onConnectionReadinessChanged?.({ ...payload, connection }, this);\n }),\n onAuthExpired: defineHook(async function ({ connectionId, expiresAt }) {\n const expired = await this.handlerTx()\n .retrieve(({ forSchema }) =>\n forSchema(apiSchema)\n .findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", connectionId)),\n )\n .findFirst(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_connection_kind\", (eb) =>\n eb.and(eb(\"connectionId\", \"=\", connectionId), eb(\"kind\", \"=\", \"auth\")),\n ),\n ),\n )\n .transformRetrieve(([connection, secret]) =>\n // The hook's due time already passed, so an untouched token is the expired one.\n connection !== null &&\n secret !== null &&\n secret.expiresAt?.getTime() === Date.parse(expiresAt) &&\n apiAuthReadyUntil(connection.authMode, secret) !== null\n ? connectionHookSnapshot(connection)\n : null,\n )\n .execute();\n if (expired) {\n await config.onConnectionReadinessChanged?.(\n { connectionId, ready: false, connection: expired },\n this,\n );\n }\n }),\n onWebhookEndpointChanged: defineHook(async function (payload) {\n await config.onWebhookEndpointChanged?.(payload, this);\n }),\n onWebhookReceived: defineHook(async function (payload) {\n await config.onWebhookReceived?.(\n {\n endpointId: payload.endpointId,\n deliveryId: payload.deliveryId,\n hookId: payload.hookId,\n receivedAt: payload.receivedAt,\n headers: payload.headers,\n query: payload.query,\n body: payload.body,\n contentType: payload.contentType,\n },\n this,\n );\n }),\n }))\n .providesBaseService(({ defineService, config }) =>\n defineService({\n startOAuth: function (input: {\n connectionId: string;\n stateId: string;\n redirectUri: string;\n scopes?: string[];\n extraAuthorizationParams?: Record<string, string>;\n discardTokens: boolean;\n }) {\n return this.serviceTx(apiSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.connectionId)),\n )\n .findFirst(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_connection_kind\", (eb) =>\n eb.and(eb(\"connectionId\", \"=\", input.connectionId), eb(\"kind\", \"=\", \"auth\")),\n ),\n ),\n )\n .transformRetrieve(async ([connection, secret]) => {\n if (!connection) {\n return { found: false as const, reason: \"connection_not_found\" as const };\n }\n if (!secret) {\n return { found: false as const, reason: \"auth_not_configured\" as const };\n }\n const auth = storedAuthPayloadSchema.parse(JSON.parse(secret.payload));\n if (auth.type !== \"oauth\") {\n return { found: false as const, reason: \"auth_not_oauth\" as const };\n }\n const snapshot = await createOAuthStartSnapshot({\n connection,\n auth,\n stateId: input.stateId,\n redirectUri: input.redirectUri,\n scopes: input.scopes,\n extraAuthorizationParams: input.extraAuthorizationParams,\n });\n // Keeps the client configuration; only consent evidence is discarded.\n const clientPayload = JSON.stringify({\n ...auth,\n tokens: undefined,\n redirectUri: undefined,\n });\n return {\n found: true as const,\n authorizationUrl: snapshot.authorizationUrl.toString(),\n oauthState: snapshot.oauthState,\n connectionId: connection.id.toString(),\n secretId: secret.id,\n clientPayload,\n discardChangesReadiness:\n isApiAuthReady(connection.authMode, secret) !==\n isApiAuthReady(connection.authMode, { payload: clientPayload, expiresAt: null }),\n };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return retrieveResult;\n }\n uow.create(\"oauthState\", {\n id: retrieveResult.oauthState.id,\n connectionId: retrieveResult.oauthState.connectionId,\n authorizationUrl: retrieveResult.oauthState.authorizationUrl,\n codeVerifier: retrieveResult.oauthState.codeVerifier,\n redirectUri: retrieveResult.oauthState.redirectUri,\n scope: retrieveResult.oauthState.scope,\n expiresAt: retrieveResult.oauthState.expiresAt,\n consumedAt: null,\n });\n if (input.discardTokens) {\n uow.update(\"secret\", retrieveResult.secretId, (b) =>\n b\n .set({\n payload: retrieveResult.clientPayload,\n expiresAt: null,\n updatedAt: b.now(),\n })\n .check(),\n );\n if (retrieveResult.discardChangesReadiness) {\n uow.triggerHook(\"onConnectionReadinessChanged\", {\n connectionId: retrieveResult.connectionId,\n ready: false,\n });\n }\n }\n return { found: true as const, authorizationUrl: retrieveResult.authorizationUrl };\n })\n .transform(({ retrieveResult }) => retrieveResult)\n .build();\n },\n\n completeOAuthCallback: function (input: { stateId: string; code: string }) {\n const stateConnectionSeparatorIndex = input.stateId.lastIndexOf(\":\");\n const connectionId =\n stateConnectionSeparatorIndex === -1\n ? \"\"\n : input.stateId.slice(0, stateConnectionSeparatorIndex);\n return this.serviceTx(apiSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"oauthState\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.stateId)),\n )\n .findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", connectionId)),\n )\n .findFirst(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_connection_kind\", (eb) =>\n eb.and(eb(\"connectionId\", \"=\", connectionId), eb(\"kind\", \"=\", \"auth\")),\n ),\n ),\n )\n .transformRetrieve(async ([state, connection, secret]) => {\n if (!state || state.consumedAt || new Date(state.expiresAt).getTime() < Date.now()) {\n return { found: false as const, reason: \"invalid_state\" as const };\n }\n if (!connection) {\n return { found: false as const, reason: \"connection_not_found\" as const };\n }\n if (!secret) {\n return { found: false as const, reason: \"auth_not_configured\" as const };\n }\n const auth = storedAuthPayloadSchema.parse(JSON.parse(secret.payload));\n if (auth.type !== \"oauth\") {\n return { found: false as const, reason: \"auth_not_oauth\" as const };\n }\n const changes = await createOAuthCallbackSnapshot({\n config,\n stateId: input.stateId,\n code: input.code,\n state,\n auth,\n });\n return {\n found: true as const,\n connection,\n changes,\n wasReady: isApiAuthReady(connection.authMode, secret),\n };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return retrieveResult;\n }\n const connectionId = retrieveResult.connection.id.toString();\n if (retrieveResult.changes.authPayload) {\n uow.update(\"secret\", `${connectionId}:auth`, (b) =>\n b.set({\n payload: retrieveResult.changes.authPayload,\n expiresAt: retrieveResult.changes.authExpiresAt ?? null,\n updatedAt: b.now(),\n }),\n );\n uow.update(\"api_connection\", retrieveResult.connection.id, (b) =>\n b.set({ authMode: \"oauth\", updatedAt: b.now() }).check(),\n );\n const connection = connectionHookSnapshot({\n ...retrieveResult.connection,\n authMode: \"oauth\",\n });\n uow.triggerHook(\"onConnectionChanged\", { connectionId, connection });\n const after = {\n payload: retrieveResult.changes.authPayload,\n expiresAt: retrieveResult.changes.authExpiresAt ?? null,\n };\n const ready = isApiAuthReady(\"oauth\", after);\n if (ready !== retrieveResult.wasReady) {\n uow.triggerHook(\"onConnectionReadinessChanged\", { connectionId, ready });\n }\n scheduleAuthExpiry(uow, connectionId, \"oauth\", after);\n }\n uow.update(\"oauthState\", input.stateId, (b) => b.set({ consumedAt: b.now() }));\n return { found: true as const };\n })\n .transform(({ retrieveResult }) => retrieveResult)\n .build();\n },\n\n executeApiRequest: function (input: { connectionId: string; request: ApiRequestInput }) {\n return this.serviceTx(apiSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.connectionId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_connection_kind\", (eb) =>\n eb(\"connectionId\", \"=\", input.connectionId),\n ),\n ),\n )\n .transformRetrieve(async ([connection, secrets]) => {\n if (!connection) {\n return { found: false as const };\n }\n if (connection.status !== \"active\") {\n return { found: false as const, reason: \"connection_disabled\" as const };\n }\n const resolved = await resolveApiOperationAuth({ config, secrets });\n const response = await performApiRequest({\n config,\n baseUrl: connection.baseUrl,\n authorization: resolved.authorization,\n request: input.request,\n });\n return {\n found: true as const,\n connection,\n secrets,\n authChanges: resolved.authChanges,\n response,\n };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return retrieveResult;\n }\n const connectionId = retrieveResult.connection.id.toString();\n const authChanges = retrieveResult.authChanges;\n if (authChanges?.authPayload) {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === \"auth\");\n if (existing) {\n uow.update(\"secret\", existing.id, (b) =>\n b\n .set({\n payload: authChanges.authPayload,\n expiresAt: authChanges.authExpiresAt ?? null,\n updatedAt: b.now(),\n })\n .check(),\n );\n } else {\n uow.create(\"secret\", {\n id: `${connectionId}:auth`,\n connectionId,\n kind: \"auth\",\n payload: authChanges.authPayload,\n expiresAt: authChanges.authExpiresAt ?? null,\n });\n }\n uow.triggerHook(\"onConnectionChanged\", {\n connectionId,\n connection: connectionHookSnapshot(retrieveResult.connection),\n });\n const { authMode } = retrieveResult.connection;\n const after = {\n payload: authChanges.authPayload,\n expiresAt: authChanges.authExpiresAt ?? null,\n };\n const ready = isApiAuthReady(authMode, after);\n if (ready !== isApiAuthReady(authMode, existing)) {\n uow.triggerHook(\"onConnectionReadinessChanged\", { connectionId, ready });\n }\n scheduleAuthExpiry(uow, connectionId, authMode, after);\n }\n return { found: true as const, response: retrieveResult.response };\n })\n .transform(({ mutateResult }) => mutateResult)\n .build();\n },\n\n refreshClientCredentialsToken: function (input: { connectionId: string }) {\n return this.serviceTx(apiSchema)\n .retrieve((uow) =>\n uow\n .findFirst(\"api_connection\", (b) =>\n b.whereIndex(\"primary\", (eb) => eb(\"id\", \"=\", input.connectionId)),\n )\n .find(\"secret\", (b) =>\n b.whereIndex(\"idx_secret_connection_kind\", (eb) =>\n eb(\"connectionId\", \"=\", input.connectionId),\n ),\n ),\n )\n .transformRetrieve(async ([connection, secrets]) => {\n if (!connection) {\n return { found: false as const };\n }\n const resolved = await resolveApiOperationAuth({ config, secrets });\n return {\n found: true as const,\n connection,\n secrets,\n authChanges: resolved.authChanges,\n tokenPresent: Boolean(resolved.authorization),\n };\n })\n .mutate(({ uow, retrieveResult }) => {\n if (!retrieveResult.found) {\n return retrieveResult;\n }\n const authChanges = retrieveResult.authChanges;\n if (authChanges?.authPayload) {\n const existing = retrieveResult.secrets.find((secret) => secret.kind === \"auth\");\n if (existing) {\n uow.update(\"secret\", existing.id, (b) =>\n b\n .set({\n payload: authChanges.authPayload,\n expiresAt: authChanges.authExpiresAt ?? null,\n updatedAt: b.now(),\n })\n .check(),\n );\n }\n const connectionId = retrieveResult.connection.id.toString();\n const connection = connectionHookSnapshot(retrieveResult.connection);\n uow.triggerHook(\"onConnectionChanged\", { connectionId, connection });\n const { authMode } = retrieveResult.connection;\n const after = {\n payload: authChanges.authPayload,\n expiresAt: authChanges.authExpiresAt ?? null,\n };\n const ready = isApiAuthReady(authMode, after);\n if (ready !== isApiAuthReady(authMode, existing)) {\n uow.triggerHook(\"onConnectionReadinessChanged\", { connectionId, ready });\n }\n scheduleAuthExpiry(uow, connectionId, authMode, after);\n }\n return { found: true as const, tokenPresent: retrieveResult.tokenPresent };\n })\n .transform(({ mutateResult }) => mutateResult)\n .build();\n },\n }),\n )\n .build();\n"],"mappings":"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"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { asciiToBase64, randomBase64Url, sha256Base64Url } from "./crypto.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/oauth.ts
|
|
4
|
+
const oauthTokensSchema = z.object({
|
|
5
|
+
accessToken: z.string().optional(),
|
|
6
|
+
refreshToken: z.string().optional(),
|
|
7
|
+
tokenType: z.string().optional(),
|
|
8
|
+
expiresIn: z.number().optional(),
|
|
9
|
+
scope: z.string().optional(),
|
|
10
|
+
idToken: z.string().optional(),
|
|
11
|
+
raw: z.record(z.string(), z.unknown()).optional()
|
|
12
|
+
});
|
|
13
|
+
const oauthTokenResponseSchema = z.looseObject({
|
|
14
|
+
access_token: z.string().optional(),
|
|
15
|
+
refresh_token: z.string().optional(),
|
|
16
|
+
token_type: z.string().optional(),
|
|
17
|
+
expires_in: z.coerce.number().optional(),
|
|
18
|
+
scope: z.string().optional(),
|
|
19
|
+
id_token: z.string().optional()
|
|
20
|
+
});
|
|
21
|
+
async function createCodeChallenge(codeVerifier) {
|
|
22
|
+
return await sha256Base64Url(codeVerifier);
|
|
23
|
+
}
|
|
24
|
+
function createAuthorizationUrl(args) {
|
|
25
|
+
const url = new URL(args.authorizationEndpoint);
|
|
26
|
+
url.searchParams.set("response_type", "code");
|
|
27
|
+
url.searchParams.set("client_id", args.clientId);
|
|
28
|
+
url.searchParams.set("redirect_uri", args.redirectUri);
|
|
29
|
+
url.searchParams.set("state", args.state);
|
|
30
|
+
url.searchParams.set("code_challenge", args.codeChallenge);
|
|
31
|
+
url.searchParams.set("code_challenge_method", "S256");
|
|
32
|
+
if (args.scopes?.length) url.searchParams.set("scope", args.scopes.join(" "));
|
|
33
|
+
for (const [key, value] of Object.entries(args.extraParams ?? {})) url.searchParams.set(key, value);
|
|
34
|
+
return url;
|
|
35
|
+
}
|
|
36
|
+
const isOAuthTokenRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
37
|
+
async function parseTokenResponse(response) {
|
|
38
|
+
const data = (response.headers.get("content-type") ?? "").includes("application/json") ? await response.json() : Object.fromEntries(new URLSearchParams(await response.text()));
|
|
39
|
+
if (!isOAuthTokenRecord(data)) {
|
|
40
|
+
if (!response.ok) throw new Error(`Token request failed with status ${response.status}`);
|
|
41
|
+
throw new Error("Token response was empty");
|
|
42
|
+
}
|
|
43
|
+
const tokenRecord = data;
|
|
44
|
+
if (!response.ok) {
|
|
45
|
+
const message = typeof tokenRecord.error_description === "string" ? tokenRecord.error_description : typeof tokenRecord.error === "string" ? tokenRecord.error : `Token request failed with status ${response.status}`;
|
|
46
|
+
throw new Error(message);
|
|
47
|
+
}
|
|
48
|
+
const parsed = oauthTokenResponseSchema.parse(tokenRecord);
|
|
49
|
+
return {
|
|
50
|
+
accessToken: parsed.access_token,
|
|
51
|
+
refreshToken: parsed.refresh_token,
|
|
52
|
+
tokenType: parsed.token_type,
|
|
53
|
+
scope: parsed.scope,
|
|
54
|
+
idToken: parsed.id_token,
|
|
55
|
+
expiresIn: parsed.expires_in,
|
|
56
|
+
raw: tokenRecord
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
function applyClientAuthentication(body, headers, auth) {
|
|
60
|
+
const method = auth.tokenEndpointAuthMethod ?? "client_secret_basic";
|
|
61
|
+
if (method === "client_secret_basic" && auth.clientSecret) {
|
|
62
|
+
headers.set("authorization", `Basic ${asciiToBase64(`${auth.clientId}:${auth.clientSecret}`, "OAuth client credentials")}`);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
body.set("client_id", auth.clientId);
|
|
66
|
+
if (auth.clientSecret && method === "client_secret_post") body.set("client_secret", auth.clientSecret);
|
|
67
|
+
}
|
|
68
|
+
async function exchangeAuthorizationCode(args) {
|
|
69
|
+
const body = new URLSearchParams({
|
|
70
|
+
grant_type: "authorization_code",
|
|
71
|
+
code: args.code,
|
|
72
|
+
redirect_uri: args.redirectUri,
|
|
73
|
+
code_verifier: args.codeVerifier
|
|
74
|
+
});
|
|
75
|
+
for (const [key, value] of Object.entries(args.auth.extraTokenParams ?? {})) body.set(key, value);
|
|
76
|
+
const headers = new Headers({
|
|
77
|
+
accept: "application/json",
|
|
78
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
79
|
+
});
|
|
80
|
+
applyClientAuthentication(body, headers, args.auth);
|
|
81
|
+
return parseTokenResponse(await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {
|
|
82
|
+
method: "POST",
|
|
83
|
+
headers,
|
|
84
|
+
body
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
async function requestClientCredentialsToken(args) {
|
|
88
|
+
const body = new URLSearchParams({ grant_type: "client_credentials" });
|
|
89
|
+
if (args.auth.scopes?.length) body.set("scope", args.auth.scopes.join(" "));
|
|
90
|
+
if (args.auth.audience) body.set("audience", args.auth.audience);
|
|
91
|
+
const headers = new Headers({
|
|
92
|
+
accept: "application/json",
|
|
93
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
94
|
+
});
|
|
95
|
+
applyClientAuthentication(body, headers, args.auth);
|
|
96
|
+
return parseTokenResponse(await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers,
|
|
99
|
+
body
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
async function refreshOAuthToken(args) {
|
|
103
|
+
const body = new URLSearchParams({
|
|
104
|
+
grant_type: "refresh_token",
|
|
105
|
+
refresh_token: args.refreshToken
|
|
106
|
+
});
|
|
107
|
+
const headers = new Headers({
|
|
108
|
+
accept: "application/json",
|
|
109
|
+
"content-type": "application/x-www-form-urlencoded"
|
|
110
|
+
});
|
|
111
|
+
applyClientAuthentication(body, headers, args.auth);
|
|
112
|
+
return parseTokenResponse(await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {
|
|
113
|
+
method: "POST",
|
|
114
|
+
headers,
|
|
115
|
+
body
|
|
116
|
+
}));
|
|
117
|
+
}
|
|
118
|
+
function tokenExpiry(tokens) {
|
|
119
|
+
return typeof tokens.expiresIn === "number" ? new Date(Date.now() + tokens.expiresIn * 1e3) : null;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
122
|
+
export { createAuthorizationUrl, createCodeChallenge, exchangeAuthorizationCode, oauthTokensSchema, randomBase64Url, refreshOAuthToken, requestClientCredentialsToken, tokenExpiry };
|
|
123
|
+
|
|
124
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","names":[],"sources":["../../src/oauth.ts"],"sourcesContent":["import { z } from \"zod\";\n\nimport type { AuthConfig } from \"./api-types\";\nimport { asciiToBase64, sha256Base64Url } from \"./crypto\";\n\nexport interface OAuthTokens {\n tokenType?: string;\n accessToken?: string;\n refreshToken?: string;\n expiresIn?: number;\n scope?: string;\n idToken?: string;\n raw?: Record<string, unknown>;\n}\n\nexport const oauthTokensSchema = z.object({\n accessToken: z.string().optional(),\n refreshToken: z.string().optional(),\n tokenType: z.string().optional(),\n expiresIn: z.number().optional(),\n scope: z.string().optional(),\n idToken: z.string().optional(),\n raw: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst oauthTokenResponseSchema = z.looseObject({\n access_token: z.string().optional(),\n refresh_token: z.string().optional(),\n token_type: z.string().optional(),\n expires_in: z.coerce.number().optional(),\n scope: z.string().optional(),\n id_token: z.string().optional(),\n});\n\nexport { bytesToBase64Url, randomBase64Url } from \"./crypto\";\n\nexport async function createCodeChallenge(codeVerifier: string) {\n return await sha256Base64Url(codeVerifier);\n}\n\nexport function createAuthorizationUrl(args: {\n authorizationEndpoint: string;\n clientId: string;\n redirectUri: string;\n state: string;\n scopes?: string[];\n codeChallenge: string;\n extraParams?: Record<string, string>;\n}) {\n const url = new URL(args.authorizationEndpoint);\n url.searchParams.set(\"response_type\", \"code\");\n url.searchParams.set(\"client_id\", args.clientId);\n url.searchParams.set(\"redirect_uri\", args.redirectUri);\n url.searchParams.set(\"state\", args.state);\n url.searchParams.set(\"code_challenge\", args.codeChallenge);\n url.searchParams.set(\"code_challenge_method\", \"S256\");\n if (args.scopes?.length) {\n url.searchParams.set(\"scope\", args.scopes.join(\" \"));\n }\n for (const [key, value] of Object.entries(args.extraParams ?? {})) {\n url.searchParams.set(key, value);\n }\n return url;\n}\n\ntype OAuthTokenRecord = Record<string, unknown> & {\n error?: unknown;\n error_description?: unknown;\n};\n\nconst isOAuthTokenRecord = (value: unknown): value is OAuthTokenRecord =>\n typeof value === \"object\" && value !== null && !Array.isArray(value);\n\nasync function parseTokenResponse(response: Response) {\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n const data: unknown = contentType.includes(\"application/json\")\n ? await response.json()\n : Object.fromEntries(new URLSearchParams(await response.text()));\n\n if (!isOAuthTokenRecord(data)) {\n if (!response.ok) {\n throw new Error(`Token request failed with status ${response.status}`);\n }\n throw new Error(\"Token response was empty\");\n }\n\n const tokenRecord = data;\n if (!response.ok) {\n const message =\n typeof tokenRecord.error_description === \"string\"\n ? tokenRecord.error_description\n : typeof tokenRecord.error === \"string\"\n ? tokenRecord.error\n : `Token request failed with status ${response.status}`;\n throw new Error(message);\n }\n\n const parsed = oauthTokenResponseSchema.parse(tokenRecord);\n return {\n accessToken: parsed.access_token,\n refreshToken: parsed.refresh_token,\n tokenType: parsed.token_type,\n scope: parsed.scope,\n idToken: parsed.id_token,\n expiresIn: parsed.expires_in,\n raw: tokenRecord,\n } satisfies OAuthTokens;\n}\n\nfunction applyClientAuthentication(\n body: URLSearchParams,\n headers: Headers,\n auth: Extract<AuthConfig, { type: \"oauth\" | \"client_credentials\" }>,\n) {\n const method = auth.tokenEndpointAuthMethod ?? \"client_secret_basic\";\n if (method === \"client_secret_basic\" && auth.clientSecret) {\n headers.set(\n \"authorization\",\n `Basic ${asciiToBase64(`${auth.clientId}:${auth.clientSecret}`, \"OAuth client credentials\")}`,\n );\n return;\n }\n body.set(\"client_id\", auth.clientId);\n if (auth.clientSecret && method === \"client_secret_post\") {\n body.set(\"client_secret\", auth.clientSecret);\n }\n}\n\nexport async function exchangeAuthorizationCode(args: {\n auth: Extract<AuthConfig, { type: \"oauth\" }>;\n code: string;\n redirectUri: string;\n codeVerifier: string;\n fetchImplementation?: typeof fetch;\n}) {\n const body = new URLSearchParams({\n grant_type: \"authorization_code\",\n code: args.code,\n redirect_uri: args.redirectUri,\n code_verifier: args.codeVerifier,\n });\n for (const [key, value] of Object.entries(args.auth.extraTokenParams ?? {})) {\n body.set(key, value);\n }\n const headers = new Headers({\n accept: \"application/json\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n });\n applyClientAuthentication(body, headers, args.auth);\n const response = await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {\n method: \"POST\",\n headers,\n body,\n });\n return parseTokenResponse(response);\n}\n\nexport async function requestClientCredentialsToken(args: {\n auth: Extract<AuthConfig, { type: \"client_credentials\" }>;\n fetchImplementation?: typeof fetch;\n}) {\n const body = new URLSearchParams({ grant_type: \"client_credentials\" });\n if (args.auth.scopes?.length) {\n body.set(\"scope\", args.auth.scopes.join(\" \"));\n }\n if (args.auth.audience) {\n body.set(\"audience\", args.auth.audience);\n }\n const headers = new Headers({\n accept: \"application/json\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n });\n applyClientAuthentication(body, headers, args.auth);\n const response = await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {\n method: \"POST\",\n headers,\n body,\n });\n return parseTokenResponse(response);\n}\n\nexport async function refreshOAuthToken(args: {\n auth: Extract<AuthConfig, { type: \"oauth\" }>;\n refreshToken: string;\n fetchImplementation?: typeof fetch;\n}) {\n const body = new URLSearchParams({\n grant_type: \"refresh_token\",\n refresh_token: args.refreshToken,\n });\n const headers = new Headers({\n accept: \"application/json\",\n \"content-type\": \"application/x-www-form-urlencoded\",\n });\n applyClientAuthentication(body, headers, args.auth);\n const response = await (args.fetchImplementation ?? fetch)(args.auth.tokenEndpoint, {\n method: \"POST\",\n headers,\n body,\n });\n return parseTokenResponse(response);\n}\n\nexport function tokenExpiry(tokens: OAuthTokens) {\n return typeof tokens.expiresIn === \"number\"\n ? new Date(Date.now() + tokens.expiresIn * 1000)\n : null;\n}\n"],"mappings":"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"}
|