@open-condo/miniapp-utils
Version:
A set of helper functions / components / hooks used to build new condo apps fast
244 lines (239 loc) • 9.81 kB
JavaScript
;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/helpers/oidc.ts
var oidc_exports = {};
__export(oidc_exports, {
OIDCMiddleware: () => OIDCMiddleware
});
module.exports = __toCommonJS(oidc_exports);
var import_openid_client = require("openid-client");
var import_zod = require("zod");
// src/helpers/ip/utils.ts
var v4Seg = "(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])";
var v4Str = `(${v4Seg}[.]){3}${v4Seg}`;
var IPv4Reg = new RegExp(`^${v4Str}$`);
var v6Seg = "(?:[0-9a-fA-F]{1,4})";
var IPv6Reg = new RegExp(
`^((?:${v6Seg}:){7}(?:${v6Seg}|:)|(?:${v6Seg}:){6}(?:${v4Str}|:${v6Seg}|:)|(?:${v6Seg}:){5}(?::${v4Str}|(:${v6Seg}){1,2}|:)|(?:${v6Seg}:){4}(?:(:${v6Seg}){0,1}:${v4Str}|(:${v6Seg}){1,3}|:)|(?:${v6Seg}:){3}(?:(:${v6Seg}){0,2}:${v4Str}|(:${v6Seg}){1,4}|:)|(?:${v6Seg}:){2}(?:(:${v6Seg}){0,3}:${v4Str}|(:${v6Seg}){1,5}|:)|(?:${v6Seg}:){1}(?:(:${v6Seg}){0,4}:${v4Str}|(:${v6Seg}){1,6}|:)|(?::((?::${v6Seg}){0,5}:${v4Str}|(?::${v6Seg}){1,7}|:)))(%[0-9a-zA-Z]{1,})?$`
);
// src/helpers/urls.ts
var REGEXP_ESCAPE_CHARS = /[\\^$.*+?()[\]{}|]/g;
var WILDCARD_REGEXP_PART = "([a-zA-Z0-9-]{1,63})";
var WILDCARD_REGEXP_PART_ESCAPED = _escapeRegexp(WILDCARD_REGEXP_PART);
function isSafeUrl(url) {
if (!url || typeof url !== "string") return false;
let decodedUrl;
try {
decodedUrl = decodeURI(url);
} catch (error) {
return false;
}
const normalizedUrl = decodedUrl.replace(/[\u0000-\u001F\s]/g, "").toLowerCase();
return !normalizedUrl.includes("javascript:");
}
function _escapeRegexp(source) {
return source.replace(REGEXP_ESCAPE_CHARS, "\\$&");
}
// src/helpers/uuid.ts
var import_crypto = require("crypto");
function generateUUIDv4() {
let randomValues;
if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") {
return crypto.randomUUID();
} else if (typeof window !== "undefined" && window.crypto && window.crypto.getRandomValues) {
randomValues = new Uint8Array(16);
window.crypto.getRandomValues(randomValues);
} else {
randomValues = (0, import_crypto.randomBytes)(16);
}
randomValues[6] = randomValues[6] & 15 | 64;
randomValues[8] = randomValues[8] & 63 | 128;
return [...randomValues].map((value, index) => {
const hex = value.toString(16).padStart(2, "0");
if (index === 4 || index === 6 || index === 8 || index === 10) {
return `-${hex}`;
}
return hex;
}).join("");
}
// src/helpers/oidc.ts
var _OIDCMiddleware = class _OIDCMiddleware {
static getQueryParams(req) {
return new URL(req.url || "/", "https://_").searchParams;
}
constructor({
getSession,
oidcConfig,
redirectUri,
logger,
onAuthSuccess,
onError,
middlewareOptions
}) {
this.getSession = getSession;
const { serverUrl, clientId, clientSecret, clientOptions, issuerOptions, scope } = oidcConfig;
const issuer = new import_openid_client.Issuer({
authorization_endpoint: `${serverUrl}/oidc/auth`,
token_endpoint: `${serverUrl}/oidc/token`,
end_session_endpoint: `${serverUrl}/oidc/session/end`,
jwks_uri: `${serverUrl}/oidc/jwks`,
revocation_endpoint: `${serverUrl}/oidc/token/revocation`,
userinfo_endpoint: `${serverUrl}/oidc/me`,
issuer: serverUrl,
...issuerOptions || {}
});
this.redirectUris = Array.isArray(redirectUri) ? redirectUri : [redirectUri];
this.middlewareOptions = middlewareOptions;
this.onAuthSuccess = onAuthSuccess;
this.onError = onError;
this.scope = scope || "openid";
this.client = new issuer.Client({
client_id: clientId,
client_secret: clientSecret,
redirect_uris: this.redirectUris,
response_types: ["code"],
token_endpoint_auth_method: "client_secret_basic",
...clientOptions || {}
});
this.logger = logger || console;
this.sendError = this.sendError.bind(this);
this.getAuthHandler = this.getAuthHandler.bind(this);
this.getCallbackHandler = this.getCallbackHandler.bind(this);
this.prepareMiddleware = this.prepareMiddleware.bind(this);
}
sendError(err, req, res, next) {
if (next && this.onError) {
this.onError(err, req, res, next);
return;
}
if (next) {
next(err);
return;
}
const errId = generateUUIDv4();
this.logger.error({ msg: "oidc auth error", errId, err });
res.writeHead(500, { "Content-Type": "text/plain" });
res.end(`OIDC auth error: ${errId}`);
}
getAuthHandler() {
const sessionGetter = this.getSession;
const client = this.client;
const sendError = this.sendError;
const scope = this.scope;
const redirectUris = this.redirectUris;
return async function authHandler(req, res, next) {
const session = await sessionGetter(req, res);
try {
const query = _OIDCMiddleware.getQueryParams(req);
const queryRedirectUri = query.get("redirect_uri");
const next2 = query.get("next");
if (next2 && isSafeUrl(next2)) {
session[_OIDCMiddleware.OIDC_NEXT_URL_KEY] = next2;
} else {
delete session[_OIDCMiddleware.OIDC_NEXT_URL_KEY];
}
const redirectUri = redirectUris.find((uri) => uri === queryRedirectUri) ?? redirectUris[0];
const checks = { nonce: import_openid_client.generators.nonce(), state: import_openid_client.generators.state() };
session[_OIDCMiddleware.OIDC_CHECKS_KEY] = { ...checks };
session[_OIDCMiddleware.OIDC_REDIRECT_URI_KEY] = redirectUri;
await session.save();
const authUrl = client.authorizationUrl({
scope,
redirect_uri: redirectUri,
...checks
});
res.writeHead(302, { Location: authUrl });
res.end();
} catch (err) {
delete session[_OIDCMiddleware.OIDC_CHECKS_KEY];
delete session[_OIDCMiddleware.OIDC_NEXT_URL_KEY];
await session.save();
return sendError(err, req, res, next);
}
};
}
getCallbackHandler() {
const sessionGetter = this.getSession;
const sendError = this.sendError;
const client = this.client;
const onAuthSuccess = this.onAuthSuccess;
const redirectUris = this.redirectUris;
return async function callbackHandler(req, res, next) {
let session = await sessionGetter(req, res);
try {
const { success, data: checks } = _OIDCMiddleware.CHECK_SCHEMA.safeParse(session[_OIDCMiddleware.OIDC_CHECKS_KEY]);
const nextUrl = session[_OIDCMiddleware.OIDC_NEXT_URL_KEY];
const redirectUri = session[_OIDCMiddleware.OIDC_REDIRECT_URI_KEY];
if (typeof redirectUri !== "string" || !redirectUris.includes(redirectUri)) {
return sendError(new Error("Invalid redirect URI"), req, res, next);
}
if (!success) {
return sendError(new Error("Invalid nonce or state"), req, res, next);
}
const params = client.callbackParams(req);
const { access_token: accessToken, refresh_token: refreshToken, id_token: idToken } = await client.callback(redirectUri, params, checks);
let userInfo;
if (accessToken) {
userInfo = await client.userinfo(accessToken);
}
delete session[_OIDCMiddleware.OIDC_CHECKS_KEY];
delete session[_OIDCMiddleware.OIDC_NEXT_URL_KEY];
await session.save();
if (onAuthSuccess) {
await onAuthSuccess(req, res, { accessToken, refreshToken, idToken, userInfo });
session = await sessionGetter(req, res);
}
session[_OIDCMiddleware.OIDC_ID_TOKEN_KEY] = idToken;
session[_OIDCMiddleware.OIDC_ACCESS_TOKEN_KEY] = accessToken;
session[_OIDCMiddleware.OIDC_REFRESH_TOKEN_KEY] = refreshToken;
await session.save();
const location = typeof nextUrl === "string" && isSafeUrl(nextUrl) ? nextUrl : "/";
res.writeHead(302, { Location: location });
res.end();
} catch (err) {
delete session[_OIDCMiddleware.OIDC_CHECKS_KEY];
delete session[_OIDCMiddleware.OIDC_NEXT_URL_KEY];
await session.save();
return sendError(err, req, res, next);
}
};
}
prepareMiddleware() {
if (!this.middlewareOptions) {
return null;
}
const { app, apiPrefix = "/api/oidc" } = this.middlewareOptions;
app.get(`${apiPrefix}/auth`, this.getAuthHandler());
app.get(`${apiPrefix}/callback`, this.getCallbackHandler());
return app;
}
};
_OIDCMiddleware.OIDC_ID_TOKEN_KEY = "oidcIdToken";
_OIDCMiddleware.OIDC_ACCESS_TOKEN_KEY = "oidcAccessToken";
_OIDCMiddleware.OIDC_REFRESH_TOKEN_KEY = "oidcRefreshToken";
_OIDCMiddleware.OIDC_NEXT_URL_KEY = "oidcNextUrl";
_OIDCMiddleware.OIDC_CHECKS_KEY = "oidcChecks";
_OIDCMiddleware.OIDC_REDIRECT_URI_KEY = "oidcRedirectUri";
_OIDCMiddleware.CHECK_SCHEMA = import_zod.z.object({ nonce: import_zod.z.string(), state: import_zod.z.string() });
var OIDCMiddleware = _OIDCMiddleware;
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
OIDCMiddleware
});
//# sourceMappingURL=oidc.js.map