@webtypen/webframez-core 0.3.36 → 0.3.38
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 +113 -0
- package/bin/webframez.cjs +8 -0
- package/dist/Ai/AiTypes.d.ts +10 -0
- package/dist/Ai/AiTypes.js +2 -0
- package/dist/Auth/AuthTypes.d.ts +1 -0
- package/dist/Auth/AuthTypes.js +2 -0
- package/dist/Auth/UserAuth.d.ts +54 -0
- package/dist/Auth/UserAuth.js +368 -0
- package/dist/Auth/UserPermissions.d.ts +13 -0
- package/dist/Auth/UserPermissions.js +56 -0
- package/dist/Auth/index.d.ts +3 -0
- package/dist/Auth/index.js +19 -0
- package/dist/Backup/BackupManager.d.ts +28 -0
- package/dist/Backup/BackupManager.js +341 -0
- package/dist/Backup/BackupOutputDrivers.d.ts +12 -0
- package/dist/Backup/BackupOutputDrivers.js +25 -0
- package/dist/Backup/BackupTypes.d.ts +107 -0
- package/dist/Backup/BackupTypes.js +2 -0
- package/dist/Backup/Jobs/BackupRunJob.d.ts +4 -0
- package/dist/Backup/Jobs/BackupRunJob.js +34 -0
- package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.d.ts +10 -0
- package/dist/Backup/OutputDrivers/BaseBackupOutputDriver.js +2 -0
- package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.d.ts +12 -0
- package/dist/Backup/OutputDrivers/LocalBackupOutputDriver.js +163 -0
- package/dist/Backup/Utils/BackupPathUtils.d.ts +8 -0
- package/dist/Backup/Utils/BackupPathUtils.js +35 -0
- package/dist/Backup/Utils/GlobMatcher.d.ts +1 -0
- package/dist/Backup/Utils/GlobMatcher.js +63 -0
- package/dist/Backup/Utils/ZipWriter.d.ts +4 -0
- package/dist/Backup/Utils/ZipWriter.js +120 -0
- package/dist/BaseKernelConsole.d.ts +4 -0
- package/dist/BaseKernelWeb.d.ts +4 -0
- package/dist/BuildApplication.d.ts +70 -0
- package/dist/BuildApplication.js +251 -0
- package/dist/Cli.d.ts +1 -0
- package/dist/Cli.js +125 -0
- package/dist/Commands/BackupCleanupCommand.d.ts +6 -0
- package/dist/Commands/BackupCleanupCommand.js +54 -0
- package/dist/Commands/BackupListCommand.d.ts +6 -0
- package/dist/Commands/BackupListCommand.js +39 -0
- package/dist/Commands/BackupQueueCommand.d.ts +6 -0
- package/dist/Commands/BackupQueueCommand.js +54 -0
- package/dist/Commands/BackupRunCommand.d.ts +6 -0
- package/dist/Commands/BackupRunCommand.js +59 -0
- package/dist/Commands/ConsoleCommand.js +30 -0
- package/dist/Commands/QueueWorkerCommand.d.ts +4 -0
- package/dist/Commands/QueueWorkerCommand.js +103 -11
- package/dist/ConsoleApplication.d.ts +2 -0
- package/dist/ConsoleApplication.js +43 -0
- package/dist/Database/BaseDBDriver.d.ts +1 -0
- package/dist/Database/BaseDBDriver.js +5 -0
- package/dist/Hooks/WebframezHooks.d.ts +38 -0
- package/dist/Hooks/WebframezHooks.js +85 -0
- package/dist/Http/HttpKernelHandlerRegistry.d.ts +20 -0
- package/dist/Http/HttpKernelHandlerRegistry.js +46 -0
- package/dist/LambdaApplication.js +36 -1
- package/dist/Mcp/McpTypes.d.ts +15 -0
- package/dist/Mcp/McpTypes.js +2 -0
- package/dist/Modules/ModuleProvider.d.ts +6 -1
- package/dist/Modules/ModuleProvider.js +2 -1
- package/dist/Modules/ModulesLoader.d.ts +6 -1
- package/dist/Modules/ModulesLoader.js +18 -2
- package/dist/Router/Router.js +182 -8
- package/dist/WebApplication.js +76 -3
- package/dist/index.d.ts +12 -0
- package/dist/index.js +12 -0
- package/package.json +5 -1
package/README.md
CHANGED
|
@@ -453,6 +453,119 @@ export default {
|
|
|
453
453
|
Additional built-in command:
|
|
454
454
|
- `build`
|
|
455
455
|
|
|
456
|
+
## Backups
|
|
457
|
+
|
|
458
|
+
Backups are configured through `config.backup`, usually by importing `config/backup.ts` next to your database and queue configs. Core ships the local filesystem output driver and a generic adapter registry.
|
|
459
|
+
|
|
460
|
+
```ts
|
|
461
|
+
export default {
|
|
462
|
+
defaults: {
|
|
463
|
+
workDir: "storage/backups/.work",
|
|
464
|
+
outputDir: "storage/backups",
|
|
465
|
+
filename: "{key}_{date}_{time}",
|
|
466
|
+
zip: true,
|
|
467
|
+
cleanupWorkDir: true,
|
|
468
|
+
retention: {
|
|
469
|
+
keepLast: 10,
|
|
470
|
+
maxAgeDays: 30,
|
|
471
|
+
runAfterBackup: true
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
types: {
|
|
475
|
+
daily_full: {
|
|
476
|
+
is_active: true,
|
|
477
|
+
files: [
|
|
478
|
+
{
|
|
479
|
+
from: "storage",
|
|
480
|
+
to: "storage",
|
|
481
|
+
include: ["**/*"],
|
|
482
|
+
exclude: ["queue/**", "backups/**"]
|
|
483
|
+
}
|
|
484
|
+
],
|
|
485
|
+
databases: [
|
|
486
|
+
{
|
|
487
|
+
connection: "default",
|
|
488
|
+
to: "database/default",
|
|
489
|
+
options: {}
|
|
490
|
+
}
|
|
491
|
+
],
|
|
492
|
+
outputs: [
|
|
493
|
+
{
|
|
494
|
+
driver: "local",
|
|
495
|
+
path: "storage/backups/daily",
|
|
496
|
+
retention: {
|
|
497
|
+
keepLast: 14,
|
|
498
|
+
maxAgeDays: 60,
|
|
499
|
+
runAfterBackup: true
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
],
|
|
503
|
+
automation: {
|
|
504
|
+
worker: "worker_1",
|
|
505
|
+
executions: [["daily", "02:00"]],
|
|
506
|
+
priority: 10
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
Built-in backup commands:
|
|
514
|
+
- `backup:list`
|
|
515
|
+
- `backup:run <key> [--dry-run] [--channel=local]`
|
|
516
|
+
- `backup:queue <key> [--worker=worker_1] [--priority=10]`
|
|
517
|
+
- `backup:cleanup <key> [--channel=local] [--dry-run]`
|
|
518
|
+
|
|
519
|
+
Remote outputs are optional packages and must be registered by the application before the backup config is used:
|
|
520
|
+
|
|
521
|
+
```ts
|
|
522
|
+
import { BackupOutputDrivers } from "@webtypen/webframez-core";
|
|
523
|
+
import { GoogleDriveBackupOutputDriver } from "@webtypen/webframez-backups-googledrive";
|
|
524
|
+
import { OneDriveBackupOutputDriver } from "@webtypen/webframez-backups-onedrive";
|
|
525
|
+
import { SftpBackupOutputDriver } from "@webtypen/webframez-backups-sftp";
|
|
526
|
+
import { FtpBackupOutputDriver } from "@webtypen/webframez-backups-ftp";
|
|
527
|
+
|
|
528
|
+
BackupOutputDrivers.register("google_drive", GoogleDriveBackupOutputDriver);
|
|
529
|
+
BackupOutputDrivers.register("one_drive", OneDriveBackupOutputDriver);
|
|
530
|
+
BackupOutputDrivers.register("sftp", SftpBackupOutputDriver);
|
|
531
|
+
BackupOutputDrivers.register("ftp", FtpBackupOutputDriver);
|
|
532
|
+
|
|
533
|
+
export default {
|
|
534
|
+
types: {
|
|
535
|
+
daily_google_drive: {
|
|
536
|
+
is_active: true,
|
|
537
|
+
files: [{ from: "storage", to: "storage", exclude: ["backups/**", "queue/**"] }],
|
|
538
|
+
outputs: [
|
|
539
|
+
{
|
|
540
|
+
driver: "google_drive",
|
|
541
|
+
folderId: process.env.GOOGLE_DRIVE_BACKUP_FOLDER_ID,
|
|
542
|
+
auth: {
|
|
543
|
+
keyFile: process.env.GOOGLE_APPLICATION_CREDENTIALS
|
|
544
|
+
},
|
|
545
|
+
retention: {
|
|
546
|
+
keepLast: 30,
|
|
547
|
+
maxAgeDays: 90,
|
|
548
|
+
runAfterBackup: true
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
],
|
|
552
|
+
automation: {
|
|
553
|
+
worker: "worker_1",
|
|
554
|
+
executions: [["daily", "03:00"], ["daily", "12:00"]]
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
Available adapter packages:
|
|
562
|
+
- `@webtypen/webframez-backups-googledrive`
|
|
563
|
+
- `@webtypen/webframez-backups-onedrive`
|
|
564
|
+
- `@webtypen/webframez-backups-sftp`
|
|
565
|
+
- `@webtypen/webframez-backups-ftp`
|
|
566
|
+
|
|
567
|
+
Database backups are delegated to the configured database driver via `backup(client, options)`. Implement that hook in your driver to write dump files into `options.targetDir`.
|
|
568
|
+
|
|
456
569
|
## Modules
|
|
457
570
|
|
|
458
571
|
You can load module providers via `modules` in `WebApplication.boot(...)`.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type KernelAiAgentRegistration = any;
|
|
2
|
+
export type KernelAiApiRegistrations = {
|
|
3
|
+
prefix?: string;
|
|
4
|
+
middleware?: string[];
|
|
5
|
+
[key: string]: any;
|
|
6
|
+
};
|
|
7
|
+
export type KernelAiRegistrations = {
|
|
8
|
+
agents?: KernelAiAgentRegistration[];
|
|
9
|
+
api?: KernelAiApiRegistrations;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type Constructor<T = {}> = new (...args: any[]) => T;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { Request } from "../Router/Request";
|
|
2
|
+
import { Constructor } from "./AuthTypes";
|
|
3
|
+
type UserAuthStrategy = "jwt" | "collection";
|
|
4
|
+
type UserAuthTokenRequestSource = "bearer" | "header" | "body" | "query";
|
|
5
|
+
export type UserAuthTokenPair = {
|
|
6
|
+
auth_token: string;
|
|
7
|
+
refresh_token: string;
|
|
8
|
+
};
|
|
9
|
+
export type UserAuthRequestTokenConfig = {
|
|
10
|
+
source?: UserAuthTokenRequestSource;
|
|
11
|
+
key?: string;
|
|
12
|
+
};
|
|
13
|
+
export type UserAuthConfig = {
|
|
14
|
+
strategy?: UserAuthStrategy;
|
|
15
|
+
primaryKey?: string;
|
|
16
|
+
jwtSecret?: string;
|
|
17
|
+
authTokenExpiresIn?: string | number;
|
|
18
|
+
refreshTokenExpiresIn?: string | number;
|
|
19
|
+
collection?: string;
|
|
20
|
+
userKey?: string;
|
|
21
|
+
authTokenKey?: string;
|
|
22
|
+
refreshTokenKey?: string;
|
|
23
|
+
authTokenExpiresAtKey?: string;
|
|
24
|
+
refreshTokenExpiresAtKey?: string;
|
|
25
|
+
authTokenRequest?: UserAuthRequestTokenConfig;
|
|
26
|
+
refreshTokenRequest?: UserAuthRequestTokenConfig;
|
|
27
|
+
};
|
|
28
|
+
export interface UserAuthConstructorMethods<TUser> {
|
|
29
|
+
auth?: UserAuthConfig;
|
|
30
|
+
refreshAuthTokenByRequest(req: Request): Promise<UserAuthTokenPair | null>;
|
|
31
|
+
getUserByRequest(req: Request): Promise<TUser | null>;
|
|
32
|
+
getUserByAuthToken(token: string): Promise<TUser | null>;
|
|
33
|
+
}
|
|
34
|
+
export interface UserAuthMethods {
|
|
35
|
+
login(): this;
|
|
36
|
+
logout(): this;
|
|
37
|
+
isAuthenticated(): boolean;
|
|
38
|
+
generateAuthToken(): Promise<UserAuthTokenPair>;
|
|
39
|
+
}
|
|
40
|
+
export declare function UserAuth<TBase extends Constructor>(Base: TBase): {
|
|
41
|
+
new (...args: any[]): {
|
|
42
|
+
login(): any;
|
|
43
|
+
logout(): any;
|
|
44
|
+
isAuthenticated(): boolean;
|
|
45
|
+
generateAuthToken(): Promise<{
|
|
46
|
+
auth_token: string;
|
|
47
|
+
refresh_token: string;
|
|
48
|
+
}>;
|
|
49
|
+
};
|
|
50
|
+
refreshAuthTokenByRequest(req: Request): Promise<any>;
|
|
51
|
+
getUserByRequest(req: Request): Promise<unknown>;
|
|
52
|
+
getUserByAuthToken(token: string): Promise<unknown>;
|
|
53
|
+
} & TBase;
|
|
54
|
+
export {};
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
25
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
26
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
27
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
28
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
29
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
30
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
31
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.UserAuth = void 0;
|
|
36
|
+
const crypto = __importStar(require("crypto"));
|
|
37
|
+
const Config_1 = require("../Config");
|
|
38
|
+
const DBConnection_1 = require("../Database/DBConnection");
|
|
39
|
+
const AUTHENTICATED_PROPERTY = "__webframez_auth_authenticated";
|
|
40
|
+
const DEFAULT_AUTH_TOKEN_EXPIRES_IN = "15m";
|
|
41
|
+
const DEFAULT_REFRESH_TOKEN_EXPIRES_IN = "30d";
|
|
42
|
+
function setAuthState(target, authenticated) {
|
|
43
|
+
Object.defineProperty(target, AUTHENTICATED_PROPERTY, {
|
|
44
|
+
value: authenticated,
|
|
45
|
+
writable: true,
|
|
46
|
+
enumerable: false,
|
|
47
|
+
configurable: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
function readBoolean(value) {
|
|
51
|
+
return typeof value === "boolean" ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
function getAuthConfig(modelClass) {
|
|
54
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v;
|
|
55
|
+
const authConfig = (_a = modelClass.auth) !== null && _a !== void 0 ? _a : {};
|
|
56
|
+
return {
|
|
57
|
+
strategy: (_b = authConfig.strategy) !== null && _b !== void 0 ? _b : "jwt",
|
|
58
|
+
primaryKey: (_c = authConfig.primaryKey) !== null && _c !== void 0 ? _c : "_id",
|
|
59
|
+
jwtSecret: (_d = authConfig.jwtSecret) !== null && _d !== void 0 ? _d : Config_1.Config.get("auth.jwtSecret"),
|
|
60
|
+
authTokenExpiresIn: (_e = authConfig.authTokenExpiresIn) !== null && _e !== void 0 ? _e : DEFAULT_AUTH_TOKEN_EXPIRES_IN,
|
|
61
|
+
refreshTokenExpiresIn: (_f = authConfig.refreshTokenExpiresIn) !== null && _f !== void 0 ? _f : DEFAULT_REFRESH_TOKEN_EXPIRES_IN,
|
|
62
|
+
collection: (_g = authConfig.collection) !== null && _g !== void 0 ? _g : "auth_tokens",
|
|
63
|
+
userKey: (_h = authConfig.userKey) !== null && _h !== void 0 ? _h : "user_id",
|
|
64
|
+
authTokenKey: (_j = authConfig.authTokenKey) !== null && _j !== void 0 ? _j : "auth_token",
|
|
65
|
+
refreshTokenKey: (_k = authConfig.refreshTokenKey) !== null && _k !== void 0 ? _k : "refresh_token",
|
|
66
|
+
authTokenExpiresAtKey: (_l = authConfig.authTokenExpiresAtKey) !== null && _l !== void 0 ? _l : "auth_token_expires_at",
|
|
67
|
+
refreshTokenExpiresAtKey: (_m = authConfig.refreshTokenExpiresAtKey) !== null && _m !== void 0 ? _m : "refresh_token_expires_at",
|
|
68
|
+
authTokenRequest: {
|
|
69
|
+
source: (_p = (_o = authConfig.authTokenRequest) === null || _o === void 0 ? void 0 : _o.source) !== null && _p !== void 0 ? _p : "bearer",
|
|
70
|
+
key: (_r = (_q = authConfig.authTokenRequest) === null || _q === void 0 ? void 0 : _q.key) !== null && _r !== void 0 ? _r : "authorization",
|
|
71
|
+
},
|
|
72
|
+
refreshTokenRequest: {
|
|
73
|
+
source: (_t = (_s = authConfig.refreshTokenRequest) === null || _s === void 0 ? void 0 : _s.source) !== null && _t !== void 0 ? _t : "header",
|
|
74
|
+
key: (_v = (_u = authConfig.refreshTokenRequest) === null || _u === void 0 ? void 0 : _u.key) !== null && _v !== void 0 ? _v : "x-refresh-token",
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function parseDurationSeconds(value) {
|
|
79
|
+
if (typeof value === "number") {
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
const match = value.match(/^(\d+)(s|m|h|d)$/);
|
|
83
|
+
if (!match) {
|
|
84
|
+
throw new Error(`Invalid auth token duration "${value}".`);
|
|
85
|
+
}
|
|
86
|
+
const amount = parseInt(match[1], 10);
|
|
87
|
+
const unit = match[2];
|
|
88
|
+
if (unit === "s") {
|
|
89
|
+
return amount;
|
|
90
|
+
}
|
|
91
|
+
if (unit === "m") {
|
|
92
|
+
return amount * 60;
|
|
93
|
+
}
|
|
94
|
+
if (unit === "h") {
|
|
95
|
+
return amount * 60 * 60;
|
|
96
|
+
}
|
|
97
|
+
return amount * 60 * 60 * 24;
|
|
98
|
+
}
|
|
99
|
+
function base64UrlEncode(value) {
|
|
100
|
+
return Buffer.from(value)
|
|
101
|
+
.toString("base64")
|
|
102
|
+
.replace(/=/g, "")
|
|
103
|
+
.replace(/\+/g, "-")
|
|
104
|
+
.replace(/\//g, "_");
|
|
105
|
+
}
|
|
106
|
+
function base64UrlDecode(value) {
|
|
107
|
+
const normalized = value.replace(/-/g, "+").replace(/_/g, "/");
|
|
108
|
+
const padded = normalized.padEnd(normalized.length + ((4 - (normalized.length % 4)) % 4), "=");
|
|
109
|
+
return Buffer.from(padded, "base64").toString("utf8");
|
|
110
|
+
}
|
|
111
|
+
function createSignature(input, secret) {
|
|
112
|
+
return base64UrlEncode(crypto.createHmac("sha256", secret).update(input).digest());
|
|
113
|
+
}
|
|
114
|
+
function getJwtSecret(config) {
|
|
115
|
+
if (!config.jwtSecret) {
|
|
116
|
+
throw new Error("Missing auth.jwtSecret config for UserAuth JWT strategy.");
|
|
117
|
+
}
|
|
118
|
+
return config.jwtSecret;
|
|
119
|
+
}
|
|
120
|
+
function createJwtToken(subject, type, expiresIn, secret) {
|
|
121
|
+
const now = Math.floor(Date.now() / 1000);
|
|
122
|
+
const payload = {
|
|
123
|
+
sub: String(subject),
|
|
124
|
+
type,
|
|
125
|
+
iat: now,
|
|
126
|
+
exp: now + parseDurationSeconds(expiresIn),
|
|
127
|
+
jti: crypto.randomBytes(16).toString("hex"),
|
|
128
|
+
};
|
|
129
|
+
const header = {
|
|
130
|
+
alg: "HS256",
|
|
131
|
+
typ: "JWT",
|
|
132
|
+
};
|
|
133
|
+
const encodedHeader = base64UrlEncode(JSON.stringify(header));
|
|
134
|
+
const encodedPayload = base64UrlEncode(JSON.stringify(payload));
|
|
135
|
+
const signingInput = `${encodedHeader}.${encodedPayload}`;
|
|
136
|
+
return `${signingInput}.${createSignature(signingInput, secret)}`;
|
|
137
|
+
}
|
|
138
|
+
function verifyJwtToken(token, expectedType, secret) {
|
|
139
|
+
const parts = token.split(".");
|
|
140
|
+
if (parts.length !== 3) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
const signingInput = `${parts[0]}.${parts[1]}`;
|
|
144
|
+
const expectedSignature = createSignature(signingInput, secret);
|
|
145
|
+
const tokenSignature = parts[2];
|
|
146
|
+
const expectedBuffer = Buffer.from(expectedSignature);
|
|
147
|
+
const tokenBuffer = Buffer.from(tokenSignature);
|
|
148
|
+
if (expectedBuffer.length !== tokenBuffer.length || !crypto.timingSafeEqual(expectedBuffer, tokenBuffer)) {
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
let payload;
|
|
152
|
+
try {
|
|
153
|
+
payload = JSON.parse(base64UrlDecode(parts[1]));
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
if (!payload || payload.type !== expectedType || !payload.sub || !payload.exp) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
if (payload.exp <= Math.floor(Date.now() / 1000)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return payload;
|
|
165
|
+
}
|
|
166
|
+
function getHeaderValue(req, key) {
|
|
167
|
+
if (!req.headers) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
const lowerKey = key.toLowerCase();
|
|
171
|
+
const foundKey = Object.keys(req.headers).find((headerKey) => headerKey.toLowerCase() === lowerKey);
|
|
172
|
+
const value = foundKey ? req.headers[foundKey] : null;
|
|
173
|
+
if (Array.isArray(value)) {
|
|
174
|
+
return value[0] ? String(value[0]) : null;
|
|
175
|
+
}
|
|
176
|
+
return value ? String(value) : null;
|
|
177
|
+
}
|
|
178
|
+
function getRequestToken(req, config) {
|
|
179
|
+
var _a, _b;
|
|
180
|
+
const source = (_a = config.source) !== null && _a !== void 0 ? _a : "header";
|
|
181
|
+
const key = (_b = config.key) !== null && _b !== void 0 ? _b : "authorization";
|
|
182
|
+
if (source === "bearer") {
|
|
183
|
+
const headerValue = getHeaderValue(req, key);
|
|
184
|
+
const match = headerValue ? headerValue.match(/^Bearer\s+(.+)$/i) : null;
|
|
185
|
+
return match && match[1] ? match[1].trim() : null;
|
|
186
|
+
}
|
|
187
|
+
if (source === "header") {
|
|
188
|
+
return getHeaderValue(req, key);
|
|
189
|
+
}
|
|
190
|
+
if (source === "body") {
|
|
191
|
+
return req.body && req.body[key] ? String(req.body[key]) : null;
|
|
192
|
+
}
|
|
193
|
+
return req.query && req.query[key] ? String(req.query[key]) : null;
|
|
194
|
+
}
|
|
195
|
+
function resolvePrimaryKeyValue(modelClass, primaryKey, value) {
|
|
196
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
197
|
+
if (primaryKey === "_id" && typeof value === "string" && modelClass.objectId) {
|
|
198
|
+
const objectId = yield modelClass.objectId(value, { noExceptions: true });
|
|
199
|
+
return objectId !== null && objectId !== void 0 ? objectId : value;
|
|
200
|
+
}
|
|
201
|
+
return value;
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
function findUserByPrimaryKey(modelClass, config, value) {
|
|
205
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
206
|
+
if (!modelClass.where) {
|
|
207
|
+
throw new Error("UserAuth requires a model with a static where(...) method.");
|
|
208
|
+
}
|
|
209
|
+
const primaryKeyValue = yield resolvePrimaryKeyValue(modelClass, config.primaryKey, value);
|
|
210
|
+
return yield modelClass.where(config.primaryKey, "=", primaryKeyValue).first();
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function getUserPrimaryKeyValue(user, config) {
|
|
214
|
+
const value = user ? user[config.primaryKey] : null;
|
|
215
|
+
if (value === undefined || value === null || value === "") {
|
|
216
|
+
throw new Error(`Cannot generate auth token without user primary key "${config.primaryKey}".`);
|
|
217
|
+
}
|
|
218
|
+
return value;
|
|
219
|
+
}
|
|
220
|
+
function createOpaqueToken() {
|
|
221
|
+
return base64UrlEncode(crypto.randomBytes(48));
|
|
222
|
+
}
|
|
223
|
+
function getExpiresAt(expiresIn) {
|
|
224
|
+
return new Date(Date.now() + parseDurationSeconds(expiresIn) * 1000);
|
|
225
|
+
}
|
|
226
|
+
function isExpired(value) {
|
|
227
|
+
if (!value) {
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
const date = value instanceof Date ? value : new Date(value);
|
|
231
|
+
return Number.isNaN(date.getTime()) || date.getTime() <= Date.now();
|
|
232
|
+
}
|
|
233
|
+
function findAuthRecord(modelClass, config, match) {
|
|
234
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
235
|
+
if (!modelClass.aggregate) {
|
|
236
|
+
throw new Error("UserAuth collection strategy requires a model with a static aggregate(...) method.");
|
|
237
|
+
}
|
|
238
|
+
const data = yield modelClass.aggregate([{ $match: match }, { $limit: 1 }], undefined, config.collection);
|
|
239
|
+
return data && data[0] ? data[0] : null;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function saveCollectionTokens(modelClass, config, user, tokens) {
|
|
243
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
244
|
+
const model = new modelClass();
|
|
245
|
+
const userId = getUserPrimaryKeyValue(user, config);
|
|
246
|
+
const existingRecord = yield findAuthRecord(modelClass, config, { [config.userKey]: userId });
|
|
247
|
+
const data = {
|
|
248
|
+
[config.userKey]: userId,
|
|
249
|
+
[config.authTokenKey]: tokens.auth_token,
|
|
250
|
+
[config.refreshTokenKey]: tokens.refresh_token,
|
|
251
|
+
[config.authTokenExpiresAtKey]: getExpiresAt(config.authTokenExpiresIn),
|
|
252
|
+
[config.refreshTokenExpiresAtKey]: getExpiresAt(config.refreshTokenExpiresIn),
|
|
253
|
+
updated_at: new Date(),
|
|
254
|
+
};
|
|
255
|
+
if (existingRecord && existingRecord._id) {
|
|
256
|
+
yield DBConnection_1.DBConnection.execute({
|
|
257
|
+
type: "updateOne",
|
|
258
|
+
table: config.collection,
|
|
259
|
+
filter: { _id: existingRecord._id },
|
|
260
|
+
data,
|
|
261
|
+
}, model.__connection);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
yield DBConnection_1.DBConnection.execute({
|
|
265
|
+
type: "insertOne",
|
|
266
|
+
table: config.collection,
|
|
267
|
+
data: Object.assign(Object.assign({}, data), { created_at: new Date() }),
|
|
268
|
+
}, model.__connection);
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
function generateCollectionTokenPair(modelClass, config, user) {
|
|
272
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
273
|
+
const tokens = {
|
|
274
|
+
auth_token: createOpaqueToken(),
|
|
275
|
+
refresh_token: createOpaqueToken(),
|
|
276
|
+
};
|
|
277
|
+
yield saveCollectionTokens(modelClass, config, user, tokens);
|
|
278
|
+
return tokens;
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
function getCollectionUserByToken(modelClass, config, token, tokenType) {
|
|
282
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
283
|
+
const tokenKey = tokenType === "auth" ? config.authTokenKey : config.refreshTokenKey;
|
|
284
|
+
const expiresAtKey = tokenType === "auth" ? config.authTokenExpiresAtKey : config.refreshTokenExpiresAtKey;
|
|
285
|
+
const authRecord = yield findAuthRecord(modelClass, config, { [tokenKey]: token });
|
|
286
|
+
if (!authRecord || isExpired(authRecord[expiresAtKey])) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
289
|
+
return yield findUserByPrimaryKey(modelClass, config, authRecord[config.userKey]);
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
function UserAuth(Base) {
|
|
293
|
+
return class extends Base {
|
|
294
|
+
static refreshAuthTokenByRequest(req) {
|
|
295
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
296
|
+
const modelClass = this;
|
|
297
|
+
const config = getAuthConfig(modelClass);
|
|
298
|
+
const refreshToken = getRequestToken(req, config.refreshTokenRequest);
|
|
299
|
+
if (!refreshToken) {
|
|
300
|
+
return null;
|
|
301
|
+
}
|
|
302
|
+
let user = null;
|
|
303
|
+
if (config.strategy === "collection") {
|
|
304
|
+
user = yield getCollectionUserByToken(modelClass, config, refreshToken, "refresh");
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const payload = verifyJwtToken(refreshToken, "refresh", getJwtSecret(config));
|
|
308
|
+
user = payload ? yield findUserByPrimaryKey(modelClass, config, payload.sub) : null;
|
|
309
|
+
}
|
|
310
|
+
return user ? yield user.generateAuthToken() : null;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
static getUserByRequest(req) {
|
|
314
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
315
|
+
const modelClass = this;
|
|
316
|
+
const config = getAuthConfig(modelClass);
|
|
317
|
+
const authToken = getRequestToken(req, config.authTokenRequest);
|
|
318
|
+
if (!authToken) {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
return yield this.getUserByAuthToken(authToken);
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
static getUserByAuthToken(token) {
|
|
325
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
326
|
+
const modelClass = this;
|
|
327
|
+
const config = getAuthConfig(modelClass);
|
|
328
|
+
if (!token) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
if (config.strategy === "collection") {
|
|
332
|
+
return yield getCollectionUserByToken(modelClass, config, token, "auth");
|
|
333
|
+
}
|
|
334
|
+
const payload = verifyJwtToken(token, "auth", getJwtSecret(config));
|
|
335
|
+
return payload ? yield findUserByPrimaryKey(modelClass, config, payload.sub) : null;
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
login() {
|
|
339
|
+
setAuthState(this, true);
|
|
340
|
+
return this;
|
|
341
|
+
}
|
|
342
|
+
logout() {
|
|
343
|
+
setAuthState(this, false);
|
|
344
|
+
return this;
|
|
345
|
+
}
|
|
346
|
+
isAuthenticated() {
|
|
347
|
+
var _a, _b, _c;
|
|
348
|
+
const target = this;
|
|
349
|
+
return ((_c = (_b = (_a = readBoolean(target[AUTHENTICATED_PROPERTY])) !== null && _a !== void 0 ? _a : readBoolean(target.authenticated)) !== null && _b !== void 0 ? _b : readBoolean(target.is_authenticated)) !== null && _c !== void 0 ? _c : false);
|
|
350
|
+
}
|
|
351
|
+
generateAuthToken() {
|
|
352
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
353
|
+
const modelClass = this.constructor;
|
|
354
|
+
const config = getAuthConfig(modelClass);
|
|
355
|
+
const userPrimaryKeyValue = getUserPrimaryKeyValue(this, config);
|
|
356
|
+
if (config.strategy === "collection") {
|
|
357
|
+
return yield generateCollectionTokenPair(modelClass, config, this);
|
|
358
|
+
}
|
|
359
|
+
const secret = getJwtSecret(config);
|
|
360
|
+
return {
|
|
361
|
+
auth_token: createJwtToken(userPrimaryKeyValue, "auth", config.authTokenExpiresIn, secret),
|
|
362
|
+
refresh_token: createJwtToken(userPrimaryKeyValue, "refresh", config.refreshTokenExpiresIn, secret),
|
|
363
|
+
};
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
};
|
|
367
|
+
}
|
|
368
|
+
exports.UserAuth = UserAuth;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { Constructor } from "./AuthTypes";
|
|
2
|
+
export interface UserPermissionsMethods {
|
|
3
|
+
can(permission: string): boolean;
|
|
4
|
+
canAny(permissions: string[]): boolean;
|
|
5
|
+
canAll(permissions: string[]): boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function UserPermissions<TBase extends Constructor>(Base: TBase): {
|
|
8
|
+
new (...args: any[]): {
|
|
9
|
+
can(permission: string): boolean;
|
|
10
|
+
canAny(permissions: string[]): boolean;
|
|
11
|
+
canAll(permissions: string[]): boolean;
|
|
12
|
+
};
|
|
13
|
+
} & TBase;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserPermissions = void 0;
|
|
4
|
+
function normalizePermissionList(value) {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return [];
|
|
7
|
+
}
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
return [value];
|
|
10
|
+
}
|
|
11
|
+
if (Array.isArray(value)) {
|
|
12
|
+
return value.flatMap((item) => normalizePermissionList(item));
|
|
13
|
+
}
|
|
14
|
+
if (value instanceof Set) {
|
|
15
|
+
return normalizePermissionList(Array.from(value));
|
|
16
|
+
}
|
|
17
|
+
if (typeof value === "object") {
|
|
18
|
+
return Object.entries(value)
|
|
19
|
+
.filter(([, enabled]) => Boolean(enabled))
|
|
20
|
+
.map(([permission]) => permission);
|
|
21
|
+
}
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
function matchesPermission(availablePermission, requestedPermission) {
|
|
25
|
+
if (availablePermission === "*" || availablePermission === requestedPermission) {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
if (!availablePermission.endsWith(".*")) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return requestedPermission.startsWith(availablePermission.slice(0, -1));
|
|
32
|
+
}
|
|
33
|
+
function hasFullAccess(target) {
|
|
34
|
+
return Boolean(target.super_admin || target.is_admin || target.admin);
|
|
35
|
+
}
|
|
36
|
+
function getPermissions(target) {
|
|
37
|
+
return [...normalizePermissionList(target.permissions), ...normalizePermissionList(target.roles)];
|
|
38
|
+
}
|
|
39
|
+
function UserPermissions(Base) {
|
|
40
|
+
return class extends Base {
|
|
41
|
+
can(permission) {
|
|
42
|
+
const target = this;
|
|
43
|
+
if (hasFullAccess(target)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return getPermissions(target).some((availablePermission) => matchesPermission(availablePermission, permission));
|
|
47
|
+
}
|
|
48
|
+
canAny(permissions) {
|
|
49
|
+
return permissions.some((permission) => this.can(permission));
|
|
50
|
+
}
|
|
51
|
+
canAll(permissions) {
|
|
52
|
+
return permissions.every((permission) => this.can(permission));
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
exports.UserPermissions = UserPermissions;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./AuthTypes"), exports);
|
|
18
|
+
__exportStar(require("./UserAuth"), exports);
|
|
19
|
+
__exportStar(require("./UserPermissions"), exports);
|