cauthy-auth 0.2.0 → 0.3.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 +12 -1
- package/dist/{cauthy-BrIMaoj1.d.mts → cauthy-C5_lopMQ.d.ts} +7 -1
- package/dist/{cauthy-CCZc9w8r.d.ts → cauthy-mNADGlNl.d.mts} +7 -1
- package/dist/cli.js +123 -2
- package/dist/fastify.d.mts +2 -2
- package/dist/fastify.d.ts +2 -2
- package/dist/index.d.mts +46 -2
- package/dist/index.d.ts +46 -2
- package/dist/index.js +243 -29
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +237 -26
- package/dist/index.mjs.map +1 -1
- package/dist/react.d.mts +3 -2
- package/dist/react.d.ts +3 -2
- package/dist/system-BWLddIt2.d.mts +233 -0
- package/dist/system-BWLddIt2.d.ts +233 -0
- package/package.json +1 -1
- package/dist/types-CYmUVWzY.d.mts +0 -106
- package/dist/types-CYmUVWzY.d.ts +0 -106
package/dist/index.mjs
CHANGED
|
@@ -54,6 +54,177 @@ async function unseal(sealed, key) {
|
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// src/system.ts
|
|
58
|
+
import { exportJWK, importJWK, importPKCS8, SignJWT } from "jose";
|
|
59
|
+
|
|
60
|
+
// src/types.ts
|
|
61
|
+
var CauthyConfigError = class extends Error {
|
|
62
|
+
constructor(message) {
|
|
63
|
+
super(`cauthy-auth: ${message}`);
|
|
64
|
+
this.name = "CauthyConfigError";
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/system.ts
|
|
69
|
+
var ASSERTION_TYPE = "urn:ietf:params:oauth:client-assertion-type:jwt-bearer";
|
|
70
|
+
var SKEW_MS = 3e4;
|
|
71
|
+
var CauthyApiError = class extends Error {
|
|
72
|
+
constructor(status, code, message) {
|
|
73
|
+
super(`cauthy-auth: ${message}`);
|
|
74
|
+
this.status = status;
|
|
75
|
+
this.code = code;
|
|
76
|
+
this.name = "CauthyApiError";
|
|
77
|
+
}
|
|
78
|
+
status;
|
|
79
|
+
code;
|
|
80
|
+
};
|
|
81
|
+
function env(name) {
|
|
82
|
+
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
83
|
+
}
|
|
84
|
+
function toOrganization(o) {
|
|
85
|
+
return {
|
|
86
|
+
id: o.id,
|
|
87
|
+
system: o.system,
|
|
88
|
+
name: o.name,
|
|
89
|
+
kind: o.kind,
|
|
90
|
+
externalId: o.external_id,
|
|
91
|
+
status: o.status,
|
|
92
|
+
metadata: o.metadata ?? {},
|
|
93
|
+
members: o.members,
|
|
94
|
+
createdAt: o.created_at,
|
|
95
|
+
updatedAt: o.updated_at
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function toMember(m) {
|
|
99
|
+
return { userId: m.user_id, email: m.email, name: m.name, status: m.status, role: m.role, ...m.invited === void 0 ? {} : { invited: m.invited } };
|
|
100
|
+
}
|
|
101
|
+
function asJwk(k) {
|
|
102
|
+
if (typeof k.privateKey !== "string") return k.privateKey;
|
|
103
|
+
return k.privateKey.includes("BEGIN") ? void 0 : JSON.parse(k.privateKey);
|
|
104
|
+
}
|
|
105
|
+
async function loadKey(k) {
|
|
106
|
+
const jwk = asJwk(k);
|
|
107
|
+
return jwk ? importJWK(jwk, "RS256") : importPKCS8(k.privateKey, "RS256", { extractable: true });
|
|
108
|
+
}
|
|
109
|
+
function createSystem(given = {}) {
|
|
110
|
+
const issuer = (given.issuer ?? env("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
111
|
+
const clientId = given.clientId ?? env("CAUTHY_CLIENT_ID") ?? "";
|
|
112
|
+
const clientSecret = given.clientSecret ?? env("CAUTHY_CLIENT_SECRET") ?? "";
|
|
113
|
+
const envKey = env("CAUTHY_SYSTEM_KEY");
|
|
114
|
+
const key = given.key ?? (envKey ? { privateKey: envKey, keyId: env("CAUTHY_SYSTEM_KEY_ID") ?? "" } : void 0);
|
|
115
|
+
const doFetch = (...args) => (given.fetch ?? fetch)(...args);
|
|
116
|
+
const tokenEndpoint = `${issuer}/v1/oauth/token`;
|
|
117
|
+
function configured() {
|
|
118
|
+
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address");
|
|
119
|
+
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
120
|
+
if (key ? key.keyId === "" : clientSecret === "") {
|
|
121
|
+
throw new CauthyConfigError(key ? "the key needs its id (CAUTHY_SYSTEM_KEY_ID)" : "to act on its own the application needs its secret (CAUTHY_CLIENT_SECRET) or a key (CAUTHY_SYSTEM_KEY)");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
let imported;
|
|
125
|
+
const signingKey = () => imported ??= loadKey(key);
|
|
126
|
+
async function fail(res, what) {
|
|
127
|
+
const body = await res.json().catch(() => null);
|
|
128
|
+
const first = body?.errors?.[0];
|
|
129
|
+
throw new CauthyApiError(res.status, first?.code ?? "error", `Cauthy refused ${what} (${res.status}): ${first?.message ?? res.statusText}`);
|
|
130
|
+
}
|
|
131
|
+
let held;
|
|
132
|
+
let asking;
|
|
133
|
+
async function ask() {
|
|
134
|
+
configured();
|
|
135
|
+
const form = new URLSearchParams({ grant_type: "client_credentials", client_id: clientId });
|
|
136
|
+
if (key) {
|
|
137
|
+
const assertion = await new SignJWT({}).setProtectedHeader({ alg: "RS256", kid: key.keyId }).setIssuer(clientId).setSubject(clientId).setAudience(tokenEndpoint).setJti(crypto.randomUUID()).setIssuedAt().setExpirationTime("2m").sign(await signingKey());
|
|
138
|
+
form.set("client_assertion_type", ASSERTION_TYPE);
|
|
139
|
+
form.set("client_assertion", assertion);
|
|
140
|
+
} else {
|
|
141
|
+
form.set("client_secret", clientSecret);
|
|
142
|
+
}
|
|
143
|
+
const res = await doFetch(tokenEndpoint, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: form });
|
|
144
|
+
if (!res.ok) await fail(res, "the application's own token");
|
|
145
|
+
const t = await res.json();
|
|
146
|
+
held = { value: t.access_token, until: Date.now() + t.expires_in * 1e3 - SKEW_MS };
|
|
147
|
+
return t.access_token;
|
|
148
|
+
}
|
|
149
|
+
function token() {
|
|
150
|
+
if (held && Date.now() < held.until) return Promise.resolve(held.value);
|
|
151
|
+
return asking ??= ask().finally(() => {
|
|
152
|
+
asking = void 0;
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
async function call(method, path, body) {
|
|
156
|
+
const send = async () => doFetch(`${issuer}${path}`, {
|
|
157
|
+
method,
|
|
158
|
+
headers: { authorization: `Bearer ${await token()}`, ...body === void 0 ? {} : { "content-type": "application/json" } },
|
|
159
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
160
|
+
});
|
|
161
|
+
let res = await send();
|
|
162
|
+
if (res.status === 401 && held) {
|
|
163
|
+
held = void 0;
|
|
164
|
+
res = await send();
|
|
165
|
+
}
|
|
166
|
+
if (!res.ok) await fail(res, `${method} ${path}`);
|
|
167
|
+
return res.status === 204 ? void 0 : res.json();
|
|
168
|
+
}
|
|
169
|
+
const data = async (method, path, body) => (await call(method, path, body))?.data;
|
|
170
|
+
const id = encodeURIComponent;
|
|
171
|
+
return {
|
|
172
|
+
token,
|
|
173
|
+
request: data,
|
|
174
|
+
organizations: {
|
|
175
|
+
async list(query = {}) {
|
|
176
|
+
const q = new URLSearchParams();
|
|
177
|
+
for (const [k, v] of Object.entries(query)) if (v !== void 0) q.set(k, String(v));
|
|
178
|
+
const out2 = await call("GET", `/v1/organizations${q.size ? `?${q}` : ""}`);
|
|
179
|
+
return { items: out2.data.map(toOrganization), total: out2.total_count };
|
|
180
|
+
},
|
|
181
|
+
async create(input) {
|
|
182
|
+
return toOrganization(await data(
|
|
183
|
+
"POST",
|
|
184
|
+
"/v1/organizations",
|
|
185
|
+
{ name: input.name, kind: input.kind, external_id: input.externalId, metadata: input.metadata }
|
|
186
|
+
));
|
|
187
|
+
},
|
|
188
|
+
async get(orgId) {
|
|
189
|
+
return toOrganization(await data("GET", `/v1/organizations/${id(orgId)}`));
|
|
190
|
+
},
|
|
191
|
+
async update(orgId, patch) {
|
|
192
|
+
return toOrganization(await data("PATCH", `/v1/organizations/${id(orgId)}`, patch));
|
|
193
|
+
},
|
|
194
|
+
async remove(orgId) {
|
|
195
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}`);
|
|
196
|
+
}
|
|
197
|
+
},
|
|
198
|
+
members: {
|
|
199
|
+
async list(orgId) {
|
|
200
|
+
return (await data("GET", `/v1/organizations/${id(orgId)}/members`)).map(toMember);
|
|
201
|
+
},
|
|
202
|
+
async add(orgId, m) {
|
|
203
|
+
return toMember(await data(
|
|
204
|
+
"POST",
|
|
205
|
+
`/v1/organizations/${id(orgId)}/members`,
|
|
206
|
+
{ role: m.role, user_id: m.userId, email: m.email, name: m.name, locale: m.locale, client_id: clientId }
|
|
207
|
+
));
|
|
208
|
+
},
|
|
209
|
+
async setRole(orgId, userId, role) {
|
|
210
|
+
return toMember(await data("PUT", `/v1/organizations/${id(orgId)}/members/${id(userId)}`, { role }));
|
|
211
|
+
},
|
|
212
|
+
async remove(orgId, userId) {
|
|
213
|
+
await call("DELETE", `/v1/organizations/${id(orgId)}/members/${id(userId)}`);
|
|
214
|
+
}
|
|
215
|
+
},
|
|
216
|
+
async invite(person) {
|
|
217
|
+
const u = await data("POST", "/v1/users", { ...person, client_id: clientId });
|
|
218
|
+
return { id: u.id, email: u.email };
|
|
219
|
+
},
|
|
220
|
+
async publicJwks() {
|
|
221
|
+
if (!key) return { keys: [] };
|
|
222
|
+
const { kty, n, e } = asJwk(key) ?? await exportJWK(await signingKey());
|
|
223
|
+
return { keys: [{ kty, n, e, kid: key.keyId, alg: "RS256", use: "sig" }] };
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
57
228
|
// src/webhook.ts
|
|
58
229
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
59
230
|
var CauthyWebhookError = class extends Error {
|
|
@@ -75,8 +246,8 @@ function keysOf(issuer) {
|
|
|
75
246
|
}
|
|
76
247
|
var hex = (s) => typeof s === "string" && s !== "" ? s : void 0;
|
|
77
248
|
async function verifyWebhook(request, options = {}) {
|
|
78
|
-
const
|
|
79
|
-
const issuer = (options.issuer ??
|
|
249
|
+
const env3 = typeof process !== "undefined" ? process.env?.CAUTHY_ISSUER : void 0;
|
|
250
|
+
const issuer = (options.issuer ?? env3 ?? "").replace(/\/+$/, "");
|
|
80
251
|
if (issuer === "") throw new CauthyWebhookError("issuer (CAUTHY_ISSUER) is not set");
|
|
81
252
|
const signature = request.headers.get("x-cauthy-signature-jwt");
|
|
82
253
|
if (!signature) throw new CauthyWebhookError("no signature");
|
|
@@ -125,20 +296,12 @@ async function verifyWebhook(request, options = {}) {
|
|
|
125
296
|
};
|
|
126
297
|
}
|
|
127
298
|
|
|
128
|
-
// src/types.ts
|
|
129
|
-
var CauthyConfigError = class extends Error {
|
|
130
|
-
constructor(message) {
|
|
131
|
-
super(`cauthy-auth: ${message}`);
|
|
132
|
-
this.name = "CauthyConfigError";
|
|
133
|
-
}
|
|
134
|
-
};
|
|
135
|
-
|
|
136
299
|
// src/cauthy.ts
|
|
137
300
|
var SESSION_DAYS = 30;
|
|
138
301
|
var FLOW_SECONDS = 600;
|
|
139
302
|
var SKEW_SECONDS = 30;
|
|
140
303
|
var ORGANIZATIONS_TTL_MS = 6e4;
|
|
141
|
-
function
|
|
304
|
+
function env2(name) {
|
|
142
305
|
return typeof process !== "undefined" ? process.env?.[name] : void 0;
|
|
143
306
|
}
|
|
144
307
|
function b64url(bytes) {
|
|
@@ -165,11 +328,11 @@ function redirect(location, cookies = []) {
|
|
|
165
328
|
return new Response(null, { status: 302, headers: h });
|
|
166
329
|
}
|
|
167
330
|
function createCauthy(given = {}) {
|
|
168
|
-
const issuer = (given.issuer ??
|
|
169
|
-
const clientId = given.clientId ??
|
|
170
|
-
const clientSecret = given.clientSecret ??
|
|
171
|
-
const secret = given.secret ??
|
|
172
|
-
const system = given.system ??
|
|
331
|
+
const issuer = (given.issuer ?? env2("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
332
|
+
const clientId = given.clientId ?? env2("CAUTHY_CLIENT_ID") ?? "";
|
|
333
|
+
const clientSecret = given.clientSecret ?? env2("CAUTHY_CLIENT_SECRET");
|
|
334
|
+
const secret = given.secret ?? env2("CAUTHY_SECRET") ?? "";
|
|
335
|
+
const system = given.system ?? env2("CAUTHY_SYSTEM") ?? "";
|
|
173
336
|
if (!/^https?:\/\//.test(issuer)) throw new CauthyConfigError("issuer (CAUTHY_ISSUER) must be Cauthy's address, e.g. https://id.example.com");
|
|
174
337
|
if (clientId === "") throw new CauthyConfigError("clientId (CAUTHY_CLIENT_ID) is required");
|
|
175
338
|
if (secret.length < 32) throw new CauthyConfigError("secret (CAUTHY_SECRET) must be at least 32 characters: it encrypts the session cookie");
|
|
@@ -438,6 +601,7 @@ function createCauthy(given = {}) {
|
|
|
438
601
|
return {
|
|
439
602
|
handler,
|
|
440
603
|
webhook,
|
|
604
|
+
system: createSystem({ issuer, clientId, clientSecret, key: given.systemKey, fetch: given.fetch }),
|
|
441
605
|
getSession,
|
|
442
606
|
status,
|
|
443
607
|
resolve: resolve2,
|
|
@@ -650,11 +814,11 @@ function toWire(theme) {
|
|
|
650
814
|
return wire;
|
|
651
815
|
}
|
|
652
816
|
async function pushTheme(o) {
|
|
653
|
-
const
|
|
654
|
-
const issuer = (o.issuer ??
|
|
655
|
-
const clientId = o.clientId ??
|
|
656
|
-
const tenant = o.tenant ??
|
|
657
|
-
const token = o.token ??
|
|
817
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
818
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
819
|
+
const clientId = o.clientId ?? env3("CAUTHY_CLIENT_ID") ?? "";
|
|
820
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
821
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
658
822
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_CLIENT_ID: clientId, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
659
823
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a theme`);
|
|
660
824
|
}
|
|
@@ -683,11 +847,11 @@ function can(organization, resource, action) {
|
|
|
683
847
|
return organization.permissions.some((p) => matches(p, resource, action));
|
|
684
848
|
}
|
|
685
849
|
async function pushPolicy(o) {
|
|
686
|
-
const
|
|
687
|
-
const issuer = (o.issuer ??
|
|
688
|
-
const system = o.system ??
|
|
689
|
-
const tenant = o.tenant ??
|
|
690
|
-
const token = o.token ??
|
|
850
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
851
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
852
|
+
const system = o.system ?? env3("CAUTHY_SYSTEM") ?? "";
|
|
853
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
854
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
691
855
|
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_SYSTEM: system, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
692
856
|
if (v === "") throw new Error(`cauthy-auth: ${name} is required to push a policy`);
|
|
693
857
|
}
|
|
@@ -701,11 +865,58 @@ async function pushPolicy(o) {
|
|
|
701
865
|
throw new Error(`cauthy-auth: Cauthy refused the policy (${res.status}): ${body?.errors?.[0]?.message ?? res.statusText}`);
|
|
702
866
|
}
|
|
703
867
|
}
|
|
868
|
+
|
|
869
|
+
// src/connect.ts
|
|
870
|
+
function sessionSecret() {
|
|
871
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
872
|
+
let s = "";
|
|
873
|
+
for (const b of bytes) s += String.fromCharCode(b);
|
|
874
|
+
return btoa(s).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
875
|
+
}
|
|
876
|
+
async function connectSystem(o) {
|
|
877
|
+
const env3 = (n) => typeof process !== "undefined" ? process.env?.[n] : void 0;
|
|
878
|
+
const issuer = (o.issuer ?? env3("CAUTHY_ISSUER") ?? "").replace(/\/+$/, "");
|
|
879
|
+
const tenant = o.tenant ?? env3("CAUTHY_TENANT") ?? "";
|
|
880
|
+
const token = o.token ?? env3("CAUTHY_TOKEN") ?? "";
|
|
881
|
+
for (const [name, v] of Object.entries({ CAUTHY_ISSUER: issuer, CAUTHY_TENANT: tenant, CAUTHY_TOKEN: token })) {
|
|
882
|
+
if (v === "") throw new Error(`cauthy-auth: ${name} is required to connect a system`);
|
|
883
|
+
}
|
|
884
|
+
const res = await (o.fetch ?? fetch)(`${issuer}/v1/connect`, {
|
|
885
|
+
method: "POST",
|
|
886
|
+
headers: { authorization: `Bearer ${token}`, "x-tenant-key": tenant, "content-type": "application/json" },
|
|
887
|
+
body: JSON.stringify({
|
|
888
|
+
system: o.system,
|
|
889
|
+
app_url: o.appUrl,
|
|
890
|
+
name: o.name,
|
|
891
|
+
callback_path: o.callbackPath,
|
|
892
|
+
events_url: o.eventsUrl,
|
|
893
|
+
events: o.events,
|
|
894
|
+
jwks_uri: o.jwksUri
|
|
895
|
+
})
|
|
896
|
+
});
|
|
897
|
+
const body = await res.json().catch(() => null);
|
|
898
|
+
if (!res.ok || !body?.data) {
|
|
899
|
+
const why = (body?.errors ?? []).map((e) => e.message).filter(Boolean).join("; ") || res.statusText;
|
|
900
|
+
throw new Error(`cauthy-auth: Cauthy did not connect the system (${res.status}): ${why}`);
|
|
901
|
+
}
|
|
902
|
+
const d = body.data;
|
|
903
|
+
return {
|
|
904
|
+
system: d.system,
|
|
905
|
+
clientId: d.client.client_id,
|
|
906
|
+
clientSecret: d.client_secret,
|
|
907
|
+
redirectUris: d.client.redirect_uris,
|
|
908
|
+
webhook: d.webhook,
|
|
909
|
+
env: { ...d.env, CAUTHY_SECRET: sessionSecret() }
|
|
910
|
+
};
|
|
911
|
+
}
|
|
704
912
|
export {
|
|
913
|
+
CauthyApiError,
|
|
705
914
|
CauthyConfigError,
|
|
706
915
|
CauthyWebhookError,
|
|
707
916
|
can,
|
|
917
|
+
connectSystem,
|
|
708
918
|
createCauthy,
|
|
919
|
+
createSystem,
|
|
709
920
|
pushPolicy,
|
|
710
921
|
pushTheme,
|
|
711
922
|
themeFromStackmyth,
|