@webda/hawk
Version:
Implements Hawk on webda
147 lines • 4.88 kB
JavaScript
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