UNPKG

@webda/hawk

Version:

Implements Hawk on webda

147 lines 4.88 kB
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; import { Core, NotEnumerable, OwnerModel, WebdaError } from "@webda/core"; import { createChecker } from "is-in-subnet"; import HawkService from "./hawk.js"; import { randomBytes } from "node:crypto"; /** * Api Key to use with hawk * * @WebdaModel */ export default class ApiKey extends OwnerModel { constructor() { super(...arguments); /** * Algorithm to use with hawk */ this.algorithm = "sha256"; } /** * Formatting structure needed for Hawk credentials * @returns {id,key,algorithm} */ toHawkCredentials() { return { id: this.uuid, key: this.__secret, algorithm: this.algorithm }; } /** * Generate secret for key * @returns */ generateSecret() { let secret = this["secret"] || randomBytes(64).toString("base64").replace(/=/g, ""); this["secret"] = undefined; return secret; } /** * @override */ async canAct(ctx, action) { // Add secret generation if not provided by input if (action === "create" && this.uuid !== "origins") { this.__secret ?? (this.__secret = this.generateSecret()); if (this.__secret.length < 32) { throw new WebdaError.BadRequest("Secret too short"); } } return super.canAct(ctx, action); } /** * Check origin masks, and returns TRUE when at least one pattern is matching to this context's origin * @param {HttpContext} ctx * @returns {boolean} TRUE if this origin is authorized with the current key */ checkOrigin(ctx) { const origin = ctx.getUniqueHeader("origin", ""); if (!this.origins || !this.origins.length) { // There is no origin contraints return true; } // Returns TRUE as soon as we found a matching authorized regexp for (let x in this.origins) { let pattern = new RegExp(this.origins[x]); if (origin.match(pattern)) { return true; } } // Origins constraints are not validated return false; } /** * Authorize access depending origin, method and permissions allowed * @param {HttpContext} ctx * @returns {boolean} TRUE if all key's contraints are authorized */ async canRequest(context) { const ctx = context.getHttpContext(); if (!this.checkOrigin(ctx)) { return false; } // Check ip whitelist if (this.whitelist) { this.__checker ?? (this.__checker = createChecker(this.whitelist.map(c => (c.indexOf("/") < 0 ? `${c}/32` : c)))); if (!this.__checker(ctx.getClientIp())) { return false; } } if (!this.permissions) { return true; } let method = ctx.getMethod(); if (method === "PATCH") { method = "PUT"; } if (!this.permissions[method]) { return false; } for (let i in this.permissions[method]) { if (ctx.getUrl().match(this.permissions[method][i])) { return true; } } return false; } /** * Update the origins in the registry */ async updateOrigins() { if (this.origins !== undefined && this.origins.length > 0) { const updates = { uuid: HawkService.RegistryEntry }; updates[`key_${this.uuid}`] = { statics: this.origins.filter((l) => !l.startsWith("regexp://")), patterns: this.origins.filter((l) => l.startsWith("regexp://")).map((l) => l.substring(9)) }; await Core.get().getRegistry().patch(updates); } else { await Core.get().getRegistry().removeAttribute(HawkService.RegistryEntry, `key_${this.uuid}`); } } /** * @override */ async _onSaved() { await this.updateOrigins(); } /** * @override */ async _onUpdated() { return this._onSaved(); } } __decorate([ NotEnumerable ], ApiKey.prototype, "__checker", void 0); export { ApiKey }; //# sourceMappingURL=apikey.js.map