@iqauth/sdk 2.3.0 → 2.5.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 +110 -0
- package/dist/browser-session.d.mts +3 -2
- package/dist/browser-session.d.ts +3 -2
- package/dist/browser.d.mts +64 -29
- package/dist/browser.d.ts +64 -29
- package/dist/browser.js +782 -38
- package/dist/browser.mjs +43 -3
- package/dist/bundle-LUKDQYVQ.mjs +374 -0
- package/dist/chunk-3JULWS6F.mjs +106 -0
- package/dist/chunk-5T7GHBX6.mjs +1165 -0
- package/dist/{chunk-KGEPDXHU.mjs → chunk-6TDJJER7.mjs} +2 -2
- package/dist/{chunk-RACIPVLD.mjs → chunk-76W5TLQQ.mjs} +262 -220
- package/dist/{chunk-EKTNEZIH.mjs → chunk-BVV54LPI.mjs} +37 -5
- package/dist/chunk-LIZYFXH7.mjs +90 -0
- package/dist/chunk-MKKZULZR.mjs +241 -0
- package/dist/chunk-SL3KRS4W.mjs +54 -0
- package/dist/chunk-TKZTCPEK.mjs +232 -0
- package/dist/chunk-UKZLOHZG.mjs +83 -0
- package/dist/cli/index.js +144 -36
- package/dist/cli/index.mjs +1 -1
- package/dist/{client-DTX4hNdS.d.ts → client-BNQe3AgF.d.ts} +3 -62
- package/dist/{client-vdh2a9fJ.d.mts → client-kYlJFgPv.d.mts} +3 -62
- package/dist/doctor-YYNHNMLD.mjs +198 -0
- package/dist/{express-A0-dWEMy.d.mts → express-B6_1vBYZ.d.mts} +23 -2
- package/dist/{express-Bo_pJKHN.d.ts → express-CHpfa7D_.d.ts} +23 -2
- package/dist/express.d.mts +5 -4
- package/dist/express.d.ts +5 -4
- package/dist/express.js +36 -4
- package/dist/express.mjs +8 -8
- package/dist/fastify.js +2 -2
- package/dist/fastify.mjs +4 -4
- package/dist/hono.js +2 -2
- package/dist/hono.mjs +4 -4
- package/dist/index.d.mts +8 -3
- package/dist/index.d.ts +8 -3
- package/dist/index.js +500 -4
- package/dist/index.mjs +29 -9
- package/dist/locales.d.mts +53 -0
- package/dist/locales.d.ts +53 -0
- package/dist/locales.js +1202 -0
- package/dist/locales.mjs +29 -0
- package/dist/mobile.d.mts +3 -2
- package/dist/mobile.d.ts +3 -2
- package/dist/next.d.mts +1 -1
- package/dist/next.d.ts +1 -1
- package/dist/next.js +2 -2
- package/dist/next.mjs +1 -1
- package/dist/provisioningBridge-88xjOS2n.d.mts +86 -0
- package/dist/provisioningBridge-DnTfzdZK.d.ts +86 -0
- package/dist/react.d.mts +1349 -10
- package/dist/react.d.ts +1349 -10
- package/dist/react.js +2985 -567
- package/dist/react.mjs +1517 -94
- package/dist/reverify-4UEJXUS6.mjs +16 -0
- package/dist/server/handlers.d.mts +10 -1
- package/dist/server/handlers.d.ts +10 -1
- package/dist/server/handlers.js +2 -2
- package/dist/server/handlers.mjs +1 -1
- package/dist/server.d.mts +5 -3
- package/dist/server.d.ts +5 -3
- package/dist/server.js +89 -4
- package/dist/server.mjs +12 -8
- package/dist/service.d.mts +3 -2
- package/dist/service.d.ts +3 -2
- package/dist/signIn-CCY4JE5G.mjs +15 -0
- package/dist/{signIn-Cd0P4y9d.d.mts → signIn-CiIBTJIh.d.mts} +224 -4
- package/dist/{signIn-DKakyzeu.d.ts → signIn-OCr88Zf8.d.ts} +224 -4
- package/dist/test.d.mts +86 -0
- package/dist/test.d.ts +86 -0
- package/dist/test.js +289 -0
- package/dist/test.mjs +9 -0
- package/dist/tokens-DCyzzn8L.d.mts +63 -0
- package/dist/tokens-aHiGFr_E.d.ts +63 -0
- package/dist/types-6bNdxesb.d.mts +196 -0
- package/dist/types-6bNdxesb.d.ts +196 -0
- package/dist/{types-Cxl3bQHt.d.mts → types-DZAflmmq.d.mts} +6 -0
- package/dist/{types-Cxl3bQHt.d.ts → types-DZAflmmq.d.ts} +6 -0
- package/dist/webhooks.d.mts +61 -0
- package/dist/webhooks.d.ts +61 -0
- package/dist/webhooks.js +119 -0
- package/dist/webhooks.mjs +11 -0
- package/dist/ws.d.mts +73 -0
- package/dist/ws.d.ts +73 -0
- package/dist/ws.js +397 -0
- package/dist/ws.mjs +12 -0
- package/package.json +22 -2
- package/dist/doctor-A5E7LSFW.mjs +0 -90
package/dist/index.js
CHANGED
|
@@ -60,13 +60,19 @@ __export(index_exports, {
|
|
|
60
60
|
TokensModule: () => TokensModule,
|
|
61
61
|
UsersModule: () => UsersModule,
|
|
62
62
|
VendorsModule: () => VendorsModule,
|
|
63
|
+
WebhookSignatureError: () => WebhookSignatureError,
|
|
63
64
|
WebhooksModule: () => WebhooksModule,
|
|
64
65
|
assertPublishableKey: () => assertPublishableKey,
|
|
66
|
+
createProvisioningBridge: () => createProvisioningBridge,
|
|
67
|
+
createTestIssuer: () => createTestIssuer,
|
|
65
68
|
encodePublishableKey: () => encodePublishableKey,
|
|
66
69
|
iqAuthMiddleware: () => iqAuthMiddleware,
|
|
67
70
|
isPublishableKey: () => isPublishableKey,
|
|
68
71
|
isSecretKey: () => isSecretKey,
|
|
69
|
-
|
|
72
|
+
isValidWebhookSignature: () => isValidWebhookSignature,
|
|
73
|
+
parsePublishableKey: () => parsePublishableKey,
|
|
74
|
+
verifyWebhookSignature: () => verifyWebhookSignature,
|
|
75
|
+
verifyWsUpgrade: () => verifyWsUpgrade
|
|
70
76
|
});
|
|
71
77
|
module.exports = __toCommonJS(index_exports);
|
|
72
78
|
|
|
@@ -1985,6 +1991,22 @@ function readCookie(req, name) {
|
|
|
1985
1991
|
}
|
|
1986
1992
|
return void 0;
|
|
1987
1993
|
}
|
|
1994
|
+
function compileMatcher(pat) {
|
|
1995
|
+
if (pat instanceof RegExp) return (p) => pat.test(p);
|
|
1996
|
+
const re = new RegExp(
|
|
1997
|
+
"^" + pat.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "::DOUBLE::").replace(/\*/g, "[^/]*").replace(/::DOUBLE::/g, ".*") + "$"
|
|
1998
|
+
);
|
|
1999
|
+
return (p) => re.test(p);
|
|
2000
|
+
}
|
|
2001
|
+
function compileMatchers(pats) {
|
|
2002
|
+
return (pats ?? []).map(compileMatcher);
|
|
2003
|
+
}
|
|
2004
|
+
function pathOf(req) {
|
|
2005
|
+
const r = req;
|
|
2006
|
+
const raw = r.path || r.originalUrl || r.url || "/";
|
|
2007
|
+
const q = raw.indexOf("?");
|
|
2008
|
+
return q >= 0 ? raw.slice(0, q) : raw;
|
|
2009
|
+
}
|
|
1988
2010
|
function clientFromPublishableKey(opts) {
|
|
1989
2011
|
const parsed = assertPublishableKey(opts.publishableKey, { context: "iqAuthMiddleware" });
|
|
1990
2012
|
const issuer = (opts.issuer ?? (parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`)).replace(/\/+$/, "");
|
|
@@ -2012,10 +2034,26 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
|
|
|
2012
2034
|
onUnauthorized,
|
|
2013
2035
|
onForbidden,
|
|
2014
2036
|
onError,
|
|
2015
|
-
|
|
2016
|
-
|
|
2037
|
+
cookieAware = true,
|
|
2038
|
+
cookieNames,
|
|
2039
|
+
protect,
|
|
2040
|
+
publicRoutes
|
|
2017
2041
|
} = resolvedOptions;
|
|
2042
|
+
const accessCookieName = resolvedOptions.accessCookieName ?? cookieNames?.access ?? DEFAULT_ACCESS_COOKIE;
|
|
2043
|
+
const protectMatchers = compileMatchers(protect);
|
|
2044
|
+
const publicMatchers = compileMatchers(publicRoutes);
|
|
2045
|
+
const hasProtect = protectMatchers.length > 0;
|
|
2046
|
+
const hasPublic = publicMatchers.length > 0;
|
|
2018
2047
|
return async (req, res, next) => {
|
|
2048
|
+
if (hasProtect || hasPublic) {
|
|
2049
|
+
const path = pathOf(req);
|
|
2050
|
+
if (hasPublic && publicMatchers.some((m) => m(path))) {
|
|
2051
|
+
return next();
|
|
2052
|
+
}
|
|
2053
|
+
if (hasProtect && !protectMatchers.some((m) => m(path))) {
|
|
2054
|
+
return next();
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2019
2057
|
let token;
|
|
2020
2058
|
const authHeader = getAuthorizationHeader(req);
|
|
2021
2059
|
if (authHeader && authHeader.startsWith("Bearer ")) {
|
|
@@ -2107,6 +2145,458 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
|
|
|
2107
2145
|
next();
|
|
2108
2146
|
};
|
|
2109
2147
|
}
|
|
2148
|
+
|
|
2149
|
+
// src/ws.ts
|
|
2150
|
+
var DEFAULT_COOKIE = "iqauth_at";
|
|
2151
|
+
var DEFAULT_SUBPROTOCOL_PREFIX = "iqauth.bearer.";
|
|
2152
|
+
var JWT_SHAPE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
|
|
2153
|
+
var tokensByIssuer = /* @__PURE__ */ new Map();
|
|
2154
|
+
function getTokens(issuer) {
|
|
2155
|
+
let mod = tokensByIssuer.get(issuer);
|
|
2156
|
+
if (!mod) {
|
|
2157
|
+
mod = new TokensModule(issuer);
|
|
2158
|
+
tokensByIssuer.set(issuer, mod);
|
|
2159
|
+
}
|
|
2160
|
+
return mod;
|
|
2161
|
+
}
|
|
2162
|
+
function firstHeader(value) {
|
|
2163
|
+
if (Array.isArray(value)) return value[0];
|
|
2164
|
+
return value;
|
|
2165
|
+
}
|
|
2166
|
+
function readCookie2(cookieHeader, name) {
|
|
2167
|
+
if (!cookieHeader) return void 0;
|
|
2168
|
+
const target = `${name}=`;
|
|
2169
|
+
for (const seg of cookieHeader.split(";")) {
|
|
2170
|
+
const t = seg.trim();
|
|
2171
|
+
if (t.startsWith(target)) {
|
|
2172
|
+
try {
|
|
2173
|
+
return decodeURIComponent(t.slice(target.length));
|
|
2174
|
+
} catch {
|
|
2175
|
+
return t.slice(target.length);
|
|
2176
|
+
}
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
return void 0;
|
|
2180
|
+
}
|
|
2181
|
+
function extractToken(req, cookieName, subprotocolPrefix) {
|
|
2182
|
+
let authHeader;
|
|
2183
|
+
let cookieHeader;
|
|
2184
|
+
let subprotoHeader;
|
|
2185
|
+
if ("headers" in req && req.headers && typeof req.headers === "object") {
|
|
2186
|
+
authHeader = firstHeader(req.headers.authorization);
|
|
2187
|
+
cookieHeader = firstHeader(req.headers.cookie);
|
|
2188
|
+
subprotoHeader = firstHeader(req.headers["sec-websocket-protocol"]);
|
|
2189
|
+
} else {
|
|
2190
|
+
const r = req;
|
|
2191
|
+
authHeader = r.authorization;
|
|
2192
|
+
cookieHeader = r.cookie;
|
|
2193
|
+
subprotoHeader = r.secWebSocketProtocol;
|
|
2194
|
+
}
|
|
2195
|
+
if (authHeader && /^Bearer /i.test(authHeader)) {
|
|
2196
|
+
return authHeader.slice(7).trim();
|
|
2197
|
+
}
|
|
2198
|
+
if (cookieName && cookieHeader) {
|
|
2199
|
+
const fromCookie = readCookie2(cookieHeader, cookieName);
|
|
2200
|
+
if (fromCookie) return fromCookie;
|
|
2201
|
+
}
|
|
2202
|
+
if (subprotocolPrefix !== null && subprotoHeader) {
|
|
2203
|
+
const protos = subprotoHeader.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2204
|
+
for (const proto of protos) {
|
|
2205
|
+
if (subprotocolPrefix && proto.startsWith(subprotocolPrefix)) {
|
|
2206
|
+
return proto.slice(subprotocolPrefix.length);
|
|
2207
|
+
}
|
|
2208
|
+
if (JWT_SHAPE.test(proto)) return proto;
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return void 0;
|
|
2212
|
+
}
|
|
2213
|
+
async function verifyWsUpgrade(req, options) {
|
|
2214
|
+
const parsed = assertPublishableKey(options.publishableKey, {
|
|
2215
|
+
context: "@iqauth/sdk/ws"
|
|
2216
|
+
});
|
|
2217
|
+
const issuer = (options.issuer && typeof options.issuer === "string" ? options.issuer : parsed.iss.startsWith("http") ? parsed.iss : `https://${parsed.iss}`).replace(/\/+$/, "");
|
|
2218
|
+
const cookieName = options.cookieName === void 0 ? DEFAULT_COOKIE : options.cookieName;
|
|
2219
|
+
const subprotocolPrefix = options.subprotocolPrefix === void 0 ? DEFAULT_SUBPROTOCOL_PREFIX : options.subprotocolPrefix;
|
|
2220
|
+
const token = extractToken(req, cookieName, subprotocolPrefix);
|
|
2221
|
+
if (!token) return null;
|
|
2222
|
+
const tokens = getTokens(issuer);
|
|
2223
|
+
try {
|
|
2224
|
+
const verifyOpts = {};
|
|
2225
|
+
if (options.audience !== void 0) verifyOpts.audience = options.audience;
|
|
2226
|
+
verifyOpts.issuer = options.issuer ?? issuer;
|
|
2227
|
+
if (options.clockTolerance !== void 0)
|
|
2228
|
+
verifyOpts.clockTolerance = options.clockTolerance;
|
|
2229
|
+
if (options.algorithms !== void 0) verifyOpts.algorithms = options.algorithms;
|
|
2230
|
+
const claims = await tokens.verify(token, verifyOpts);
|
|
2231
|
+
return { claims };
|
|
2232
|
+
} catch (err) {
|
|
2233
|
+
if (err instanceof IQAuthError) return null;
|
|
2234
|
+
return null;
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
|
|
2238
|
+
// src/test.ts
|
|
2239
|
+
var import_http2 = require("http");
|
|
2240
|
+
var import_crypto2 = require("crypto");
|
|
2241
|
+
var import_jsonwebtoken = __toESM(require("jsonwebtoken"));
|
|
2242
|
+
function jwkFromPublicKey(publicKey, kid) {
|
|
2243
|
+
const jwk = publicKey.export({ format: "jwk" });
|
|
2244
|
+
return { kty: "RSA", use: "sig", alg: "RS256", kid, n: jwk.n, e: jwk.e };
|
|
2245
|
+
}
|
|
2246
|
+
function readBody(req) {
|
|
2247
|
+
return new Promise((resolve, reject) => {
|
|
2248
|
+
const chunks = [];
|
|
2249
|
+
req.on("data", (c) => chunks.push(c));
|
|
2250
|
+
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")));
|
|
2251
|
+
req.on("error", reject);
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
function parseFormOrJson(raw, contentType) {
|
|
2255
|
+
if (!raw) return {};
|
|
2256
|
+
if (contentType && contentType.includes("application/json")) {
|
|
2257
|
+
try {
|
|
2258
|
+
const obj = JSON.parse(raw);
|
|
2259
|
+
const out2 = {};
|
|
2260
|
+
for (const [k, v] of Object.entries(obj || {})) {
|
|
2261
|
+
if (typeof v === "string") out2[k] = v;
|
|
2262
|
+
else if (v != null) out2[k] = String(v);
|
|
2263
|
+
}
|
|
2264
|
+
return out2;
|
|
2265
|
+
} catch {
|
|
2266
|
+
return {};
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
const out = {};
|
|
2270
|
+
for (const part of raw.split("&")) {
|
|
2271
|
+
if (!part) continue;
|
|
2272
|
+
const eq = part.indexOf("=");
|
|
2273
|
+
const k = decodeURIComponent(eq === -1 ? part : part.slice(0, eq)).replace(/\+/g, " ");
|
|
2274
|
+
const v = eq === -1 ? "" : decodeURIComponent(part.slice(eq + 1)).replace(/\+/g, " ");
|
|
2275
|
+
out[k] = v;
|
|
2276
|
+
}
|
|
2277
|
+
return out;
|
|
2278
|
+
}
|
|
2279
|
+
function send(res, status, body, headers = {}) {
|
|
2280
|
+
const payload = typeof body === "string" ? body : JSON.stringify(body);
|
|
2281
|
+
res.writeHead(status, {
|
|
2282
|
+
"Content-Type": typeof body === "string" ? "text/plain; charset=utf-8" : "application/json; charset=utf-8",
|
|
2283
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
2284
|
+
...headers
|
|
2285
|
+
});
|
|
2286
|
+
res.end(payload);
|
|
2287
|
+
}
|
|
2288
|
+
async function createTestIssuer(options = {}) {
|
|
2289
|
+
const host = options.host ?? "127.0.0.1";
|
|
2290
|
+
const port = options.port ?? 0;
|
|
2291
|
+
const tenantId = options.tenantId ?? "tenant-test";
|
|
2292
|
+
const appId = options.appId ?? "app-test";
|
|
2293
|
+
const kid = options.kid ?? `test-${(0, import_crypto2.randomBytes)(6).toString("hex")}`;
|
|
2294
|
+
const defaultAudience = options.defaultAudience ?? ["dispositioniq"];
|
|
2295
|
+
const { privateKey, publicKey } = (0, import_crypto2.generateKeyPairSync)("rsa", {
|
|
2296
|
+
modulusLength: 2048,
|
|
2297
|
+
publicKeyEncoding: { type: "spki", format: "pem" },
|
|
2298
|
+
privateKeyEncoding: { type: "pkcs8", format: "pem" }
|
|
2299
|
+
});
|
|
2300
|
+
const publicKeyObj = (0, import_crypto2.createPublicKey)(publicKey);
|
|
2301
|
+
const jwk = jwkFromPublicKey(publicKeyObj, kid);
|
|
2302
|
+
const pendingCodes = /* @__PURE__ */ new Map();
|
|
2303
|
+
let baseUrl = "";
|
|
2304
|
+
const buildToken = (opts) => {
|
|
2305
|
+
const payload = {
|
|
2306
|
+
sub: opts.sub ?? "test-user",
|
|
2307
|
+
email: opts.email ?? "test@example.com",
|
|
2308
|
+
name: opts.name ?? "Test User",
|
|
2309
|
+
tenantId: opts.tenantId ?? tenantId,
|
|
2310
|
+
vendorId: opts.vendorId ?? null,
|
|
2311
|
+
roles: opts.roles ?? [],
|
|
2312
|
+
entitlements: opts.entitlements ?? [],
|
|
2313
|
+
sessionId: opts.sessionId ?? `sess-${(0, import_crypto2.randomBytes)(4).toString("hex")}`,
|
|
2314
|
+
jti: opts.jti ?? `jti-${(0, import_crypto2.randomBytes)(4).toString("hex")}`
|
|
2315
|
+
};
|
|
2316
|
+
if (opts.scopeContext !== void 0) payload.scopeContext = opts.scopeContext;
|
|
2317
|
+
if (opts.loginMethod !== void 0) payload.loginMethod = opts.loginMethod;
|
|
2318
|
+
for (const [k, v] of Object.entries(opts)) {
|
|
2319
|
+
if (["sub", "email", "name", "tenantId", "vendorId", "roles", "entitlements", "sessionId", "jti", "scopeContext", "loginMethod", "audience", "issuer", "expiresInSeconds", "iat"].includes(k))
|
|
2320
|
+
continue;
|
|
2321
|
+
payload[k] = v;
|
|
2322
|
+
}
|
|
2323
|
+
const audience = opts.audience ?? defaultAudience;
|
|
2324
|
+
const issuer = opts.issuer ?? baseUrl;
|
|
2325
|
+
const expiresIn = opts.expiresInSeconds ?? 900;
|
|
2326
|
+
const signOpts = {
|
|
2327
|
+
algorithm: "RS256",
|
|
2328
|
+
keyid: kid,
|
|
2329
|
+
issuer,
|
|
2330
|
+
audience
|
|
2331
|
+
};
|
|
2332
|
+
if (opts.iat !== void 0) {
|
|
2333
|
+
payload.iat = opts.iat;
|
|
2334
|
+
payload.exp = opts.iat + expiresIn;
|
|
2335
|
+
} else {
|
|
2336
|
+
signOpts.expiresIn = expiresIn;
|
|
2337
|
+
}
|
|
2338
|
+
return import_jsonwebtoken.default.sign(payload, privateKey, signOpts);
|
|
2339
|
+
};
|
|
2340
|
+
const handler = async (req, res) => {
|
|
2341
|
+
try {
|
|
2342
|
+
const url = new URL(req.url || "/", baseUrl || `http://${host}`);
|
|
2343
|
+
const path = url.pathname;
|
|
2344
|
+
if (req.method === "OPTIONS") {
|
|
2345
|
+
res.writeHead(204, {
|
|
2346
|
+
"Access-Control-Allow-Origin": "*",
|
|
2347
|
+
"Access-Control-Allow-Methods": "GET,POST,OPTIONS",
|
|
2348
|
+
"Access-Control-Allow-Headers": "Authorization,Content-Type"
|
|
2349
|
+
});
|
|
2350
|
+
return res.end();
|
|
2351
|
+
}
|
|
2352
|
+
const cors = { "Access-Control-Allow-Origin": "*" };
|
|
2353
|
+
if (req.method === "GET" && path === "/.well-known/openid-configuration") {
|
|
2354
|
+
return send(res, 200, {
|
|
2355
|
+
issuer: baseUrl,
|
|
2356
|
+
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
|
|
2357
|
+
authorization_endpoint: `${baseUrl}/oidc/authorize`,
|
|
2358
|
+
token_endpoint: `${baseUrl}/oidc/token`,
|
|
2359
|
+
userinfo_endpoint: `${baseUrl}/api/v1/auth/me`,
|
|
2360
|
+
response_types_supported: ["code"],
|
|
2361
|
+
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
2362
|
+
subject_types_supported: ["public"],
|
|
2363
|
+
id_token_signing_alg_values_supported: ["RS256"],
|
|
2364
|
+
code_challenge_methods_supported: ["S256"]
|
|
2365
|
+
}, cors);
|
|
2366
|
+
}
|
|
2367
|
+
if (req.method === "GET" && path === "/.well-known/jwks.json") {
|
|
2368
|
+
return send(res, 200, { keys: [jwk] }, { ...cors, "Cache-Control": "public, max-age=3600" });
|
|
2369
|
+
}
|
|
2370
|
+
if (req.method === "POST" && path === "/oidc/token") {
|
|
2371
|
+
const raw = await readBody(req);
|
|
2372
|
+
const params = parseFormOrJson(raw, req.headers["content-type"]);
|
|
2373
|
+
const grant = params.grant_type;
|
|
2374
|
+
if (grant === "authorization_code") {
|
|
2375
|
+
const code = params.code;
|
|
2376
|
+
const pending = code ? pendingCodes.get(code) : void 0;
|
|
2377
|
+
if (!pending) {
|
|
2378
|
+
return send(res, 400, { error: "invalid_grant", error_description: "Unknown or expired code" }, cors);
|
|
2379
|
+
}
|
|
2380
|
+
pendingCodes.delete(code);
|
|
2381
|
+
const accessToken = buildToken(pending.claims);
|
|
2382
|
+
return send(res, 200, {
|
|
2383
|
+
access_token: accessToken,
|
|
2384
|
+
refresh_token: pending.refreshToken,
|
|
2385
|
+
id_token: accessToken,
|
|
2386
|
+
token_type: "Bearer",
|
|
2387
|
+
expires_in: pending.claims.expiresInSeconds ?? 900
|
|
2388
|
+
}, cors);
|
|
2389
|
+
}
|
|
2390
|
+
if (grant === "refresh_token") {
|
|
2391
|
+
const accessToken = buildToken({ sub: "test-user" });
|
|
2392
|
+
return send(res, 200, {
|
|
2393
|
+
access_token: accessToken,
|
|
2394
|
+
refresh_token: params.refresh_token || `rt-${(0, import_crypto2.randomBytes)(8).toString("hex")}`,
|
|
2395
|
+
token_type: "Bearer",
|
|
2396
|
+
expires_in: 900
|
|
2397
|
+
}, cors);
|
|
2398
|
+
}
|
|
2399
|
+
return send(res, 400, { error: "unsupported_grant_type" }, cors);
|
|
2400
|
+
}
|
|
2401
|
+
if (req.method === "GET" && path === "/api/v1/auth/me") {
|
|
2402
|
+
const auth = req.headers.authorization || "";
|
|
2403
|
+
if (!/^Bearer /i.test(auth)) {
|
|
2404
|
+
return send(res, 401, { success: false, error: { code: "TOKEN_INVALID", message: "Missing bearer" } }, cors);
|
|
2405
|
+
}
|
|
2406
|
+
const token = auth.slice(7).trim();
|
|
2407
|
+
try {
|
|
2408
|
+
const decoded = import_jsonwebtoken.default.verify(token, publicKey, {
|
|
2409
|
+
algorithms: ["RS256"],
|
|
2410
|
+
issuer: baseUrl,
|
|
2411
|
+
audience: defaultAudience
|
|
2412
|
+
});
|
|
2413
|
+
return send(res, 200, {
|
|
2414
|
+
success: true,
|
|
2415
|
+
data: {
|
|
2416
|
+
id: decoded.sub,
|
|
2417
|
+
email: decoded.email,
|
|
2418
|
+
name: decoded.name,
|
|
2419
|
+
tenantId: decoded.tenantId,
|
|
2420
|
+
roles: decoded.roles,
|
|
2421
|
+
entitlements: decoded.entitlements
|
|
2422
|
+
}
|
|
2423
|
+
}, cors);
|
|
2424
|
+
} catch (err) {
|
|
2425
|
+
const msg = err instanceof Error ? err.message : "verify failed";
|
|
2426
|
+
return send(res, 401, { success: false, error: { code: "TOKEN_INVALID", message: msg } }, cors);
|
|
2427
|
+
}
|
|
2428
|
+
}
|
|
2429
|
+
send(res, 404, { error: "not_found", path }, cors);
|
|
2430
|
+
} catch (err) {
|
|
2431
|
+
const msg = err instanceof Error ? err.message : "internal error";
|
|
2432
|
+
send(res, 500, { error: "internal", message: msg });
|
|
2433
|
+
}
|
|
2434
|
+
};
|
|
2435
|
+
const server = (0, import_http2.createServer)((req, res) => {
|
|
2436
|
+
void handler(req, res);
|
|
2437
|
+
});
|
|
2438
|
+
await new Promise((resolve, reject) => {
|
|
2439
|
+
server.once("error", reject);
|
|
2440
|
+
server.listen(port, host, () => {
|
|
2441
|
+
server.off("error", reject);
|
|
2442
|
+
resolve();
|
|
2443
|
+
});
|
|
2444
|
+
});
|
|
2445
|
+
const addr = server.address();
|
|
2446
|
+
const boundPort = typeof addr === "object" && addr ? addr.port : port;
|
|
2447
|
+
baseUrl = `http://${host}:${boundPort}`;
|
|
2448
|
+
const publishableKey = encodePublishableKey("test", {
|
|
2449
|
+
iss: baseUrl,
|
|
2450
|
+
appId,
|
|
2451
|
+
tenantId,
|
|
2452
|
+
kid
|
|
2453
|
+
});
|
|
2454
|
+
return {
|
|
2455
|
+
baseUrl,
|
|
2456
|
+
publishableKey,
|
|
2457
|
+
kid,
|
|
2458
|
+
publicKey,
|
|
2459
|
+
mintToken: (opts = {}) => buildToken(opts),
|
|
2460
|
+
mintAuthCode: (opts = {}) => {
|
|
2461
|
+
const code = `code-${(0, import_crypto2.randomBytes)(12).toString("hex")}`;
|
|
2462
|
+
const refreshToken = opts.refreshToken ?? `rt-${(0, import_crypto2.randomBytes)(12).toString("hex")}`;
|
|
2463
|
+
pendingCodes.set(code, { claims: opts, refreshToken });
|
|
2464
|
+
return code;
|
|
2465
|
+
},
|
|
2466
|
+
close: () => new Promise((resolve, reject) => {
|
|
2467
|
+
server.close((err) => err ? reject(err) : resolve());
|
|
2468
|
+
})
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
// src/webhooks.ts
|
|
2473
|
+
var import_crypto3 = __toESM(require("crypto"));
|
|
2474
|
+
var WebhookSignatureError = class extends Error {
|
|
2475
|
+
constructor(code, message) {
|
|
2476
|
+
super(message);
|
|
2477
|
+
this.name = "WebhookSignatureError";
|
|
2478
|
+
this.code = code;
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
function toBuffer(p) {
|
|
2482
|
+
if (typeof p === "string") return Buffer.from(p, "utf8");
|
|
2483
|
+
if (Buffer.isBuffer(p)) return p;
|
|
2484
|
+
return Buffer.from(p);
|
|
2485
|
+
}
|
|
2486
|
+
function parseHeader(header) {
|
|
2487
|
+
let t = NaN;
|
|
2488
|
+
const v1 = [];
|
|
2489
|
+
for (const part of header.split(",")) {
|
|
2490
|
+
const [k, v] = part.split("=", 2);
|
|
2491
|
+
if (!k || v === void 0) continue;
|
|
2492
|
+
const key = k.trim();
|
|
2493
|
+
const value = v.trim();
|
|
2494
|
+
if (key === "t") t = Number(value);
|
|
2495
|
+
else if (key === "v1") v1.push(value);
|
|
2496
|
+
}
|
|
2497
|
+
return { t, v1 };
|
|
2498
|
+
}
|
|
2499
|
+
function timingSafeEqualHex(a, b) {
|
|
2500
|
+
if (a.length !== b.length) return false;
|
|
2501
|
+
try {
|
|
2502
|
+
return import_crypto3.default.timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
2503
|
+
} catch {
|
|
2504
|
+
return false;
|
|
2505
|
+
}
|
|
2506
|
+
}
|
|
2507
|
+
function verifyWebhookSignature(opts) {
|
|
2508
|
+
const headerRaw = Array.isArray(opts.header) ? opts.header[0] : opts.header;
|
|
2509
|
+
if (!headerRaw || typeof headerRaw !== "string") {
|
|
2510
|
+
throw new WebhookSignatureError("MISSING_HEADER", "Missing X-IQAuth-Signature header");
|
|
2511
|
+
}
|
|
2512
|
+
if (!opts.secret) {
|
|
2513
|
+
throw new WebhookSignatureError("MISSING_SECRET", "secret is required");
|
|
2514
|
+
}
|
|
2515
|
+
const { t, v1 } = parseHeader(headerRaw);
|
|
2516
|
+
if (!Number.isFinite(t) || v1.length === 0) {
|
|
2517
|
+
throw new WebhookSignatureError("MALFORMED_HEADER", `Could not parse signature header: ${headerRaw}`);
|
|
2518
|
+
}
|
|
2519
|
+
const tolerance = opts.toleranceSeconds ?? 300;
|
|
2520
|
+
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
2521
|
+
if (Math.abs(now - t) > tolerance) {
|
|
2522
|
+
throw new WebhookSignatureError(
|
|
2523
|
+
"TIMESTAMP_OUT_OF_TOLERANCE",
|
|
2524
|
+
`Signature timestamp ${t} is outside the ${tolerance}s tolerance window (now=${now})`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
const body = toBuffer(opts.payload);
|
|
2528
|
+
const expected = import_crypto3.default.createHmac("sha256", opts.secret).update(`${t}.`).update(body).digest("hex");
|
|
2529
|
+
const matched = v1.some((sig) => timingSafeEqualHex(sig, expected));
|
|
2530
|
+
if (!matched) {
|
|
2531
|
+
throw new WebhookSignatureError("SIGNATURE_MISMATCH", "Webhook signature does not match expected value");
|
|
2532
|
+
}
|
|
2533
|
+
let parsed;
|
|
2534
|
+
try {
|
|
2535
|
+
parsed = JSON.parse(body.toString("utf8"));
|
|
2536
|
+
} catch {
|
|
2537
|
+
throw new WebhookSignatureError("MALFORMED_BODY", "Webhook body is not valid JSON");
|
|
2538
|
+
}
|
|
2539
|
+
return parsed;
|
|
2540
|
+
}
|
|
2541
|
+
function isValidWebhookSignature(opts) {
|
|
2542
|
+
try {
|
|
2543
|
+
verifyWebhookSignature(opts);
|
|
2544
|
+
return true;
|
|
2545
|
+
} catch {
|
|
2546
|
+
return false;
|
|
2547
|
+
}
|
|
2548
|
+
}
|
|
2549
|
+
|
|
2550
|
+
// src/server/provisioningBridge.ts
|
|
2551
|
+
function defaultIsUniqueViolation(err) {
|
|
2552
|
+
if (!err || typeof err !== "object") return false;
|
|
2553
|
+
const e = err;
|
|
2554
|
+
if (e.code === "23505") return true;
|
|
2555
|
+
if (typeof e.code === "string" && e.code.startsWith("SQLITE_CONSTRAINT")) return true;
|
|
2556
|
+
if (typeof e.message === "string" && /unique constraint|duplicate key/i.test(e.message)) return true;
|
|
2557
|
+
return false;
|
|
2558
|
+
}
|
|
2559
|
+
function createProvisioningBridge(options) {
|
|
2560
|
+
const { storage } = options;
|
|
2561
|
+
const isUniqueViolation = options.isUniqueViolation ?? defaultIsUniqueViolation;
|
|
2562
|
+
const roleOf = (claims) => {
|
|
2563
|
+
try {
|
|
2564
|
+
return options.roleMapper?.(claims) ?? null;
|
|
2565
|
+
} catch {
|
|
2566
|
+
return null;
|
|
2567
|
+
}
|
|
2568
|
+
};
|
|
2569
|
+
const ensureUser = async (claims) => {
|
|
2570
|
+
if (!claims?.sub) {
|
|
2571
|
+
throw new Error("createProvisioningBridge: claims.sub is required");
|
|
2572
|
+
}
|
|
2573
|
+
const byId = await storage.findByIqAuthUserId(claims.sub);
|
|
2574
|
+
if (byId) return { user: byId, claims, created: false, adopted: false };
|
|
2575
|
+
if (claims.email) {
|
|
2576
|
+
const byEmail = await storage.findByEmail(claims.email);
|
|
2577
|
+
if (byEmail) {
|
|
2578
|
+
if (storage.adoptByEmail) {
|
|
2579
|
+
const adopted = await storage.adoptByEmail(byEmail, claims, roleOf(claims));
|
|
2580
|
+
return { user: adopted, claims, created: false, adopted: true };
|
|
2581
|
+
}
|
|
2582
|
+
}
|
|
2583
|
+
}
|
|
2584
|
+
try {
|
|
2585
|
+
const created = await storage.insertFromClaims(claims, roleOf(claims));
|
|
2586
|
+
return { user: created, claims, created: true, adopted: false };
|
|
2587
|
+
} catch (err) {
|
|
2588
|
+
if (!isUniqueViolation(err)) throw err;
|
|
2589
|
+
const after = await storage.findByIqAuthUserId(claims.sub);
|
|
2590
|
+
if (after) return { user: after, claims, created: false, adopted: false };
|
|
2591
|
+
if (claims.email) {
|
|
2592
|
+
const byEmail = await storage.findByEmail(claims.email);
|
|
2593
|
+
if (byEmail) return { user: byEmail, claims, created: false, adopted: true };
|
|
2594
|
+
}
|
|
2595
|
+
throw err;
|
|
2596
|
+
}
|
|
2597
|
+
};
|
|
2598
|
+
return { ensureUser };
|
|
2599
|
+
}
|
|
2110
2600
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2111
2601
|
0 && (module.exports = {
|
|
2112
2602
|
ApiKeysModule,
|
|
@@ -2139,11 +2629,17 @@ function iqAuthMiddleware(clientOrOptions, options = {}) {
|
|
|
2139
2629
|
TokensModule,
|
|
2140
2630
|
UsersModule,
|
|
2141
2631
|
VendorsModule,
|
|
2632
|
+
WebhookSignatureError,
|
|
2142
2633
|
WebhooksModule,
|
|
2143
2634
|
assertPublishableKey,
|
|
2635
|
+
createProvisioningBridge,
|
|
2636
|
+
createTestIssuer,
|
|
2144
2637
|
encodePublishableKey,
|
|
2145
2638
|
iqAuthMiddleware,
|
|
2146
2639
|
isPublishableKey,
|
|
2147
2640
|
isSecretKey,
|
|
2148
|
-
|
|
2641
|
+
isValidWebhookSignature,
|
|
2642
|
+
parsePublishableKey,
|
|
2643
|
+
verifyWebhookSignature,
|
|
2644
|
+
verifyWsUpgrade
|
|
2149
2645
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createProvisioningBridge
|
|
3
|
+
} from "./chunk-SL3KRS4W.mjs";
|
|
4
|
+
import {
|
|
5
|
+
createTestIssuer
|
|
6
|
+
} from "./chunk-MKKZULZR.mjs";
|
|
7
|
+
import {
|
|
8
|
+
WebhookSignatureError,
|
|
9
|
+
isValidWebhookSignature,
|
|
10
|
+
verifyWebhookSignature
|
|
11
|
+
} from "./chunk-UKZLOHZG.mjs";
|
|
12
|
+
import {
|
|
13
|
+
verifyWsUpgrade
|
|
14
|
+
} from "./chunk-3JULWS6F.mjs";
|
|
1
15
|
import {
|
|
2
16
|
iqAuthMiddleware
|
|
3
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BVV54LPI.mjs";
|
|
18
|
+
import {
|
|
19
|
+
assertPublishableKey,
|
|
20
|
+
encodePublishableKey,
|
|
21
|
+
isPublishableKey,
|
|
22
|
+
isSecretKey,
|
|
23
|
+
parsePublishableKey
|
|
24
|
+
} from "./chunk-WQWBJSSS.mjs";
|
|
4
25
|
import {
|
|
5
26
|
ApiKeysModule,
|
|
6
27
|
AppsModule,
|
|
@@ -28,13 +49,6 @@ import {
|
|
|
28
49
|
VendorsModule,
|
|
29
50
|
WebhooksModule
|
|
30
51
|
} from "./chunk-W3F4JYGP.mjs";
|
|
31
|
-
import {
|
|
32
|
-
assertPublishableKey,
|
|
33
|
-
encodePublishableKey,
|
|
34
|
-
isPublishableKey,
|
|
35
|
-
isSecretKey,
|
|
36
|
-
parsePublishableKey
|
|
37
|
-
} from "./chunk-WQWBJSSS.mjs";
|
|
38
52
|
import {
|
|
39
53
|
DEFAULT_CLOCK_TOLERANCE_SECONDS,
|
|
40
54
|
DEFAULT_TOKEN_AUDIENCE,
|
|
@@ -77,11 +91,17 @@ export {
|
|
|
77
91
|
TokensModule,
|
|
78
92
|
UsersModule,
|
|
79
93
|
VendorsModule,
|
|
94
|
+
WebhookSignatureError,
|
|
80
95
|
WebhooksModule,
|
|
81
96
|
assertPublishableKey,
|
|
97
|
+
createProvisioningBridge,
|
|
98
|
+
createTestIssuer,
|
|
82
99
|
encodePublishableKey,
|
|
83
100
|
iqAuthMiddleware,
|
|
84
101
|
isPublishableKey,
|
|
85
102
|
isSecretKey,
|
|
86
|
-
|
|
103
|
+
isValidWebhookSignature,
|
|
104
|
+
parsePublishableKey,
|
|
105
|
+
verifyWebhookSignature,
|
|
106
|
+
verifyWsUpgrade
|
|
87
107
|
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { I as IQAuthLocaleBundle, a as IQAuthLocaleOverride, b as IQAuthLocaleKey } from './types-6bNdxesb.mjs';
|
|
2
|
+
|
|
3
|
+
declare const enUS: IQAuthLocaleBundle;
|
|
4
|
+
|
|
5
|
+
declare const frFR: IQAuthLocaleBundle;
|
|
6
|
+
|
|
7
|
+
declare const esES: IQAuthLocaleBundle;
|
|
8
|
+
|
|
9
|
+
declare const deDE: IQAuthLocaleBundle;
|
|
10
|
+
|
|
11
|
+
declare const jaJP: IQAuthLocaleBundle;
|
|
12
|
+
|
|
13
|
+
declare const ptBR: IQAuthLocaleBundle;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Public entry point for `@iqauth/sdk/locales`.
|
|
17
|
+
*
|
|
18
|
+
* Exports the strict bundle type, the default `enUS` bundle, the five other
|
|
19
|
+
* shipped locales, the `t()` resolver, and helpers for error-code mapping
|
|
20
|
+
* and Accept-Language negotiation.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** Bundles indexed by their canonical BCP-47 tag. */
|
|
24
|
+
declare const builtInLocales: Record<string, IQAuthLocaleBundle>;
|
|
25
|
+
/**
|
|
26
|
+
* Default bundle used when a key is missing from a contributor-supplied
|
|
27
|
+
* partial override, or when no `localization` prop is passed at all.
|
|
28
|
+
*/
|
|
29
|
+
declare const defaultBundle: IQAuthLocaleBundle;
|
|
30
|
+
/**
|
|
31
|
+
* Resolve a localized string from a bundle. Substitutes `{name}` placeholders
|
|
32
|
+
* with values from `vars`. If the key is missing from the bundle (which
|
|
33
|
+
* should never happen when contributors supply a fully typed bundle, but can
|
|
34
|
+
* happen with a `Partial<IQAuthLocaleBundle>` override), the default `enUS`
|
|
35
|
+
* value is used as a graceful fallback.
|
|
36
|
+
*/
|
|
37
|
+
declare function t(bundle: IQAuthLocaleBundle | IQAuthLocaleOverride | undefined | null, key: IQAuthLocaleKey, vars?: Record<string, string | number>): string;
|
|
38
|
+
/**
|
|
39
|
+
* Merge a partial override on top of the default bundle so callers always
|
|
40
|
+
* receive a fully populated `IQAuthLocaleBundle`. Used internally by
|
|
41
|
+
* `<IQAuthProvider localization={...}>`.
|
|
42
|
+
*/
|
|
43
|
+
declare function resolveBundle(override?: IQAuthLocaleBundle | IQAuthLocaleOverride | null): IQAuthLocaleBundle;
|
|
44
|
+
declare function localizeErrorCode(bundle: IQAuthLocaleBundle | IQAuthLocaleOverride | undefined | null, code: string | undefined | null, vars?: Record<string, string | number>): string;
|
|
45
|
+
/**
|
|
46
|
+
* Negotiate the best matching locale tag from a comma-separated
|
|
47
|
+
* `Accept-Language` header (or `navigator.language` value), preferring exact
|
|
48
|
+
* matches and falling back to the language subtag (e.g. `fr-CA` -> `fr-FR`).
|
|
49
|
+
* Returns `"en-US"` when no candidate matches a built-in locale.
|
|
50
|
+
*/
|
|
51
|
+
declare function negotiateLocale(acceptLanguage: string | null | undefined, available?: string[]): string;
|
|
52
|
+
|
|
53
|
+
export { IQAuthLocaleBundle, IQAuthLocaleKey, IQAuthLocaleOverride, builtInLocales, deDE, defaultBundle, enUS, esES, frFR, jaJP, localizeErrorCode, negotiateLocale, ptBR, resolveBundle, t };
|