@vizamodo/modo-dispatcher 1.1.77
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 +43 -0
- package/dist/artifacts/builders.d.ts +32 -0
- package/dist/artifacts/builders.js +72 -0
- package/dist/artifacts/downloader.d.ts +28 -0
- package/dist/artifacts/downloader.js +102 -0
- package/dist/artifacts/normalizer.d.ts +22 -0
- package/dist/artifacts/normalizer.js +65 -0
- package/dist/artifacts/types.d.ts +94 -0
- package/dist/artifacts/types.js +1 -0
- package/dist/auth/identity/extract-identity.d.ts +8 -0
- package/dist/auth/identity/extract-identity.js +15 -0
- package/dist/auth/identity/normalize-teams.d.ts +1 -0
- package/dist/auth/identity/normalize-teams.js +5 -0
- package/dist/auth/identity/validate-claims.d.ts +2 -0
- package/dist/auth/identity/validate-claims.js +5 -0
- package/dist/auth/jwt/parse.d.ts +2 -0
- package/dist/auth/jwt/parse.js +16 -0
- package/dist/auth/jwt/verify.d.ts +1 -0
- package/dist/auth/jwt/verify.js +9 -0
- package/dist/auth/oauth/auth-error.d.ts +12 -0
- package/dist/auth/oauth/auth-error.js +47 -0
- package/dist/auth/oauth/auth-session-store.d.ts +30 -0
- package/dist/auth/oauth/auth-session-store.js +46 -0
- package/dist/auth/oauth/callback-server.d.ts +21 -0
- package/dist/auth/oauth/callback-server.js +319 -0
- package/dist/auth/oauth/github.d.ts +14 -0
- package/dist/auth/oauth/github.js +100 -0
- package/dist/auth/oauth/sse.d.ts +11 -0
- package/dist/auth/oauth/sse.js +67 -0
- package/dist/auth/oauth/templates/failed.html +629 -0
- package/dist/auth/oauth/templates/pending.html +620 -0
- package/dist/auth/oauth/templates/success.html +577 -0
- package/dist/auth/session/access-token.d.ts +15 -0
- package/dist/auth/session/access-token.js +64 -0
- package/dist/auth/session/login.d.ts +18 -0
- package/dist/auth/session/login.js +144 -0
- package/dist/auth/session/logout.d.ts +3 -0
- package/dist/auth/session/logout.js +21 -0
- package/dist/auth/session/refresh-token.d.ts +8 -0
- package/dist/auth/session/refresh-token.js +16 -0
- package/dist/auth/session/refresh.d.ts +2 -0
- package/dist/auth/session/refresh.js +61 -0
- package/dist/auth/session/rotate.d.ts +4 -0
- package/dist/auth/session/rotate.js +4 -0
- package/dist/auth/session/session-manager.d.ts +16 -0
- package/dist/auth/session/session-manager.js +54 -0
- package/dist/auth/session/token-state.d.ts +20 -0
- package/dist/auth/session/token-state.js +55 -0
- package/dist/auth/session/types.d.ts +35 -0
- package/dist/auth/session/types.js +1 -0
- package/dist/auth/storage/keychain.d.ts +16 -0
- package/dist/auth/storage/keychain.js +107 -0
- package/dist/auth/storage/memory.d.ts +10 -0
- package/dist/auth/storage/memory.js +15 -0
- package/dist/auth/storage/types.d.ts +5 -0
- package/dist/auth/storage/types.js +1 -0
- package/dist/config/defaults.d.ts +94 -0
- package/dist/config/defaults.js +116 -0
- package/dist/core/auth-bootstrap.d.ts +15 -0
- package/dist/core/auth-bootstrap.js +33 -0
- package/dist/core/bootstrap.d.ts +73 -0
- package/dist/core/bootstrap.js +248 -0
- package/dist/core/dispatcher.d.ts +2 -0
- package/dist/core/dispatcher.js +28 -0
- package/dist/core/ensure-bootstrap.d.ts +3 -0
- package/dist/core/ensure-bootstrap.js +25 -0
- package/dist/core/errors.d.ts +69 -0
- package/dist/core/errors.js +121 -0
- package/dist/core/runtime-orchestrator.d.ts +17 -0
- package/dist/core/runtime-orchestrator.js +47 -0
- package/dist/core/runtime.d.ts +16 -0
- package/dist/core/runtime.js +52 -0
- package/dist/core/validation.d.ts +2 -0
- package/dist/core/validation.js +21 -0
- package/dist/crypto/encrypt.d.ts +12 -0
- package/dist/crypto/encrypt.js +80 -0
- package/dist/flows/email-otp-flow.d.ts +7 -0
- package/dist/flows/email-otp-flow.js +249 -0
- package/dist/gateway/auth-header.d.ts +2 -0
- package/dist/gateway/auth-header.js +21 -0
- package/dist/gateway/client.d.ts +7 -0
- package/dist/gateway/client.js +24 -0
- package/dist/gateway/dispatch-with-bootstrap-retry.d.ts +25 -0
- package/dist/gateway/dispatch-with-bootstrap-retry.js +157 -0
- package/dist/gateway/dispatch.d.ts +23 -0
- package/dist/gateway/dispatch.js +110 -0
- package/dist/gateway/session-waiter.d.ts +11 -0
- package/dist/gateway/session-waiter.js +126 -0
- package/dist/gateway/session.d.ts +21 -0
- package/dist/gateway/session.js +74 -0
- package/dist/gateway/types.d.ts +113 -0
- package/dist/gateway/types.js +19 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +21 -0
- package/dist/runtime/runtime-context.d.ts +6 -0
- package/dist/runtime/runtime-context.js +1 -0
- package/dist/types/dispatcher.d.ts +275 -0
- package/dist/types/dispatcher.js +1 -0
- package/dist/types/payload.d.ts +5 -0
- package/dist/types/payload.js +2 -0
- package/dist/types/runtime-env.d.ts +2 -0
- package/dist/types/runtime-env.js +5 -0
- package/dist/ui/banner.d.ts +4 -0
- package/dist/ui/banner.js +20 -0
- package/dist/utils/request-dedup.d.ts +75 -0
- package/dist/utils/request-dedup.js +102 -0
- package/dist/utils/type-guards.d.ts +173 -0
- package/dist/utils/type-guards.js +232 -0
- package/package.json +43 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for cli-dispatcher
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - Deterministic exit codes for viza-cli
|
|
6
|
+
* - Fail-fast, no side effects
|
|
7
|
+
* - Machine-readable + human-readable
|
|
8
|
+
*/
|
|
9
|
+
export declare enum DispatcherExitCode {
|
|
10
|
+
OK = 0,
|
|
11
|
+
INVALID_INPUT = 2,
|
|
12
|
+
GITHUB_NOT_LOGGED_IN = 10,
|
|
13
|
+
GITHUB_FORBIDDEN = 11,
|
|
14
|
+
DISPATCH_REJECTED = 20,
|
|
15
|
+
SESSION_TIMEOUT = 21,
|
|
16
|
+
GATEWAY_ERROR = 22,
|
|
17
|
+
ARTIFACT_NOT_FOUND = 30,
|
|
18
|
+
ARTIFACT_TOO_LARGE = 31,
|
|
19
|
+
ARTIFACT_INVALID_TYPE = 32,
|
|
20
|
+
INTERNAL_ERROR = 90
|
|
21
|
+
}
|
|
22
|
+
export interface DispatcherErrorOptions {
|
|
23
|
+
cause?: unknown;
|
|
24
|
+
details?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Base dispatcher error
|
|
28
|
+
*/
|
|
29
|
+
export declare class DispatcherError extends Error {
|
|
30
|
+
readonly exitCode: DispatcherExitCode;
|
|
31
|
+
readonly details?: Record<string, any>;
|
|
32
|
+
readonly cause?: unknown;
|
|
33
|
+
constructor(message: string, exitCode: DispatcherExitCode, options?: DispatcherErrorOptions);
|
|
34
|
+
}
|
|
35
|
+
export declare class InvalidInputError extends DispatcherError {
|
|
36
|
+
constructor(message: string, options?: DispatcherErrorOptions);
|
|
37
|
+
}
|
|
38
|
+
export declare class GitHubNotLoggedInError extends DispatcherError {
|
|
39
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
40
|
+
}
|
|
41
|
+
export declare class GitHubForbiddenError extends DispatcherError {
|
|
42
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
43
|
+
}
|
|
44
|
+
export declare class DispatchRejectedError extends DispatcherError {
|
|
45
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
46
|
+
}
|
|
47
|
+
export declare class SessionTimeoutError extends DispatcherError {
|
|
48
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
49
|
+
}
|
|
50
|
+
export declare class GatewayError extends DispatcherError {
|
|
51
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
52
|
+
}
|
|
53
|
+
export declare class ArtifactNotFoundError extends DispatcherError {
|
|
54
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
55
|
+
}
|
|
56
|
+
export declare class ArtifactTooLargeError extends DispatcherError {
|
|
57
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
58
|
+
}
|
|
59
|
+
export declare class ArtifactInvalidTypeError extends DispatcherError {
|
|
60
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
61
|
+
}
|
|
62
|
+
export declare class InternalDispatcherError extends DispatcherError {
|
|
63
|
+
constructor(message?: string, options?: DispatcherErrorOptions);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Normalize unknown errors into DispatcherError
|
|
67
|
+
* Used by viza-cli boundary
|
|
68
|
+
*/
|
|
69
|
+
export declare function normalizeDispatcherError(err: unknown): DispatcherError;
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error taxonomy for cli-dispatcher
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - Deterministic exit codes for viza-cli
|
|
6
|
+
* - Fail-fast, no side effects
|
|
7
|
+
* - Machine-readable + human-readable
|
|
8
|
+
*/
|
|
9
|
+
export var DispatcherExitCode;
|
|
10
|
+
(function (DispatcherExitCode) {
|
|
11
|
+
DispatcherExitCode[DispatcherExitCode["OK"] = 0] = "OK";
|
|
12
|
+
// Input / usage
|
|
13
|
+
DispatcherExitCode[DispatcherExitCode["INVALID_INPUT"] = 2] = "INVALID_INPUT";
|
|
14
|
+
// Auth / permission
|
|
15
|
+
DispatcherExitCode[DispatcherExitCode["GITHUB_NOT_LOGGED_IN"] = 10] = "GITHUB_NOT_LOGGED_IN";
|
|
16
|
+
DispatcherExitCode[DispatcherExitCode["GITHUB_FORBIDDEN"] = 11] = "GITHUB_FORBIDDEN";
|
|
17
|
+
// Network / gateway
|
|
18
|
+
DispatcherExitCode[DispatcherExitCode["DISPATCH_REJECTED"] = 20] = "DISPATCH_REJECTED";
|
|
19
|
+
DispatcherExitCode[DispatcherExitCode["SESSION_TIMEOUT"] = 21] = "SESSION_TIMEOUT";
|
|
20
|
+
DispatcherExitCode[DispatcherExitCode["GATEWAY_ERROR"] = 22] = "GATEWAY_ERROR";
|
|
21
|
+
// Artifacts / logs
|
|
22
|
+
DispatcherExitCode[DispatcherExitCode["ARTIFACT_NOT_FOUND"] = 30] = "ARTIFACT_NOT_FOUND";
|
|
23
|
+
DispatcherExitCode[DispatcherExitCode["ARTIFACT_TOO_LARGE"] = 31] = "ARTIFACT_TOO_LARGE";
|
|
24
|
+
DispatcherExitCode[DispatcherExitCode["ARTIFACT_INVALID_TYPE"] = 32] = "ARTIFACT_INVALID_TYPE";
|
|
25
|
+
// Internal / unexpected
|
|
26
|
+
DispatcherExitCode[DispatcherExitCode["INTERNAL_ERROR"] = 90] = "INTERNAL_ERROR";
|
|
27
|
+
})(DispatcherExitCode || (DispatcherExitCode = {}));
|
|
28
|
+
/**
|
|
29
|
+
* Base dispatcher error
|
|
30
|
+
*/
|
|
31
|
+
export class DispatcherError extends Error {
|
|
32
|
+
exitCode;
|
|
33
|
+
details;
|
|
34
|
+
cause;
|
|
35
|
+
constructor(message, exitCode, options) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = this.constructor.name;
|
|
38
|
+
this.exitCode = exitCode;
|
|
39
|
+
if (options?.details !== undefined) {
|
|
40
|
+
this.details = options.details;
|
|
41
|
+
}
|
|
42
|
+
this.cause = options?.cause;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/* -----------------------------
|
|
46
|
+
* Input / validation
|
|
47
|
+
* ----------------------------- */
|
|
48
|
+
export class InvalidInputError extends DispatcherError {
|
|
49
|
+
constructor(message, options) {
|
|
50
|
+
super(message, DispatcherExitCode.INVALID_INPUT, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/* -----------------------------
|
|
54
|
+
* GitHub auth
|
|
55
|
+
* ----------------------------- */
|
|
56
|
+
export class GitHubNotLoggedInError extends DispatcherError {
|
|
57
|
+
constructor(message = "Not logged in to GitHub", options) {
|
|
58
|
+
super(message, DispatcherExitCode.GITHUB_NOT_LOGGED_IN, options);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export class GitHubForbiddenError extends DispatcherError {
|
|
62
|
+
constructor(message = "GitHub permission denied", options) {
|
|
63
|
+
super(message, DispatcherExitCode.GITHUB_FORBIDDEN, options);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/* -----------------------------
|
|
67
|
+
* Gateway / dispatch
|
|
68
|
+
* ----------------------------- */
|
|
69
|
+
export class DispatchRejectedError extends DispatcherError {
|
|
70
|
+
constructor(message = "Dispatch request rejected", options) {
|
|
71
|
+
super(message, DispatcherExitCode.DISPATCH_REJECTED, options);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
export class SessionTimeoutError extends DispatcherError {
|
|
75
|
+
constructor(message = "Dispatch session timed out", options) {
|
|
76
|
+
super(message, DispatcherExitCode.SESSION_TIMEOUT, options);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
export class GatewayError extends DispatcherError {
|
|
80
|
+
constructor(message = "Gateway error", options) {
|
|
81
|
+
super(message, DispatcherExitCode.GATEWAY_ERROR, options);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/* -----------------------------
|
|
85
|
+
* Artifacts
|
|
86
|
+
* ----------------------------- */
|
|
87
|
+
export class ArtifactNotFoundError extends DispatcherError {
|
|
88
|
+
constructor(message = "Artifact not found. Possible cause: gateway R2 credentials rotated. Please redeploy the dispatch gateway and retry.", options) {
|
|
89
|
+
super(message, DispatcherExitCode.ARTIFACT_NOT_FOUND, options);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export class ArtifactTooLargeError extends DispatcherError {
|
|
93
|
+
constructor(message = "Artifact exceeds size limit", options) {
|
|
94
|
+
super(message, DispatcherExitCode.ARTIFACT_TOO_LARGE, options);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export class ArtifactInvalidTypeError extends DispatcherError {
|
|
98
|
+
constructor(message = "Invalid artifact content type", options) {
|
|
99
|
+
super(message, DispatcherExitCode.ARTIFACT_INVALID_TYPE, options);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/* -----------------------------
|
|
103
|
+
* Internal
|
|
104
|
+
* ----------------------------- */
|
|
105
|
+
export class InternalDispatcherError extends DispatcherError {
|
|
106
|
+
constructor(message = "Internal dispatcher error", options) {
|
|
107
|
+
super(message, DispatcherExitCode.INTERNAL_ERROR, options);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Normalize unknown errors into DispatcherError
|
|
112
|
+
* Used by viza-cli boundary
|
|
113
|
+
*/
|
|
114
|
+
export function normalizeDispatcherError(err) {
|
|
115
|
+
if (err instanceof DispatcherError)
|
|
116
|
+
return err;
|
|
117
|
+
if (err instanceof Error) {
|
|
118
|
+
return new InternalDispatcherError(err.message, { cause: err });
|
|
119
|
+
}
|
|
120
|
+
return new InternalDispatcherError("Unknown error", { cause: err });
|
|
121
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { DispatchInput, DispatchMode, DispatchOptions, DispatchResult } from "../types/dispatcher.js";
|
|
2
|
+
import { RuntimeContext } from "../runtime/runtime-context.js";
|
|
3
|
+
export interface DispatchLifecycleResult {
|
|
4
|
+
runtimeContext: RuntimeContext;
|
|
5
|
+
result: DispatchResult;
|
|
6
|
+
sessionId: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Full dispatch lifecycle orchestration.
|
|
10
|
+
*
|
|
11
|
+
* Pipeline:
|
|
12
|
+
* 1. Auth & Bootstrap
|
|
13
|
+
* 2. Render banner
|
|
14
|
+
* 3. Dispatch to gateway (with bootstrap retry)
|
|
15
|
+
* 4. Wait for session result
|
|
16
|
+
*/
|
|
17
|
+
export declare function executeDispatchLifecycle(input: DispatchInput, options: DispatchOptions, mode: DispatchMode): Promise<DispatchLifecycleResult>;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { ensureAuthAndBootstrap } from "./auth-bootstrap.js";
|
|
2
|
+
import { dispatchWithBootstrapRetry } from "../gateway/dispatch-with-bootstrap-retry.js";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
import { extractSessionId } from "../utils/type-guards.js";
|
|
5
|
+
import { renderDispatchBanner } from "../ui/banner.js";
|
|
6
|
+
import { waitResultFromAccepted } from "../gateway/session-waiter.js";
|
|
7
|
+
/**
|
|
8
|
+
* Full dispatch lifecycle orchestration.
|
|
9
|
+
*
|
|
10
|
+
* Pipeline:
|
|
11
|
+
* 1. Auth & Bootstrap
|
|
12
|
+
* 2. Render banner
|
|
13
|
+
* 3. Dispatch to gateway (with bootstrap retry)
|
|
14
|
+
* 4. Wait for session result
|
|
15
|
+
*/
|
|
16
|
+
export async function executeDispatchLifecycle(input, options, mode) {
|
|
17
|
+
// 1. Auth & Bootstrap
|
|
18
|
+
const { runtimeContext, bootstrapCfg } = await ensureAuthAndBootstrap(options);
|
|
19
|
+
// 2. Render banner
|
|
20
|
+
if (options.banner !== false) {
|
|
21
|
+
renderDispatchBanner(runtimeContext.claims.login ?? "unknown", runtimeContext.teams, input.allowedTeams, mode === "status");
|
|
22
|
+
}
|
|
23
|
+
// 3. Dispatch to gateway
|
|
24
|
+
const dispatched = await dispatchWithBootstrapRetry({
|
|
25
|
+
input,
|
|
26
|
+
mode,
|
|
27
|
+
teams: runtimeContext.teams,
|
|
28
|
+
bootstrap: bootstrapCfg,
|
|
29
|
+
targetEnv: options.auth.targetEnv,
|
|
30
|
+
});
|
|
31
|
+
const acceptedSessionId = extractSessionId(dispatched.accepted.artifacts) ?? "runtime";
|
|
32
|
+
if (dispatched.accepted.action === "dispatch" ||
|
|
33
|
+
dispatched.accepted.action === "reuse") {
|
|
34
|
+
const actionLabel = dispatched.accepted.action === "reuse"
|
|
35
|
+
? chalk.yellow("REUSE")
|
|
36
|
+
: chalk.green("NEW");
|
|
37
|
+
console.log(chalk.gray("\n✓ Dispatch session established"));
|
|
38
|
+
console.log(`${chalk.gray("Session:")} ${acceptedSessionId}`);
|
|
39
|
+
console.log(`${chalk.gray("Mode :")} ${actionLabel}`);
|
|
40
|
+
}
|
|
41
|
+
// 4. Wait for session result
|
|
42
|
+
return {
|
|
43
|
+
runtimeContext,
|
|
44
|
+
result: await waitResultFromAccepted(dispatched.accepted, input),
|
|
45
|
+
sessionId: acceptedSessionId,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AccessTokenService } from "../auth/session/access-token.js";
|
|
2
|
+
import { TokenStateMachine } from "../auth/session/token-state.js";
|
|
3
|
+
import { RefreshTokenService } from "../auth/session/refresh-token.js";
|
|
4
|
+
export interface AuthRuntimeConfig {
|
|
5
|
+
refreshEndpoint: string;
|
|
6
|
+
}
|
|
7
|
+
export interface AuthRuntime {
|
|
8
|
+
accessTokenService: AccessTokenService;
|
|
9
|
+
refreshTokenService: RefreshTokenService;
|
|
10
|
+
tokenState: TokenStateMachine;
|
|
11
|
+
getBootstrapEndpoint(): string;
|
|
12
|
+
}
|
|
13
|
+
export declare function initAuthRuntime(config: AuthRuntimeConfig): AuthRuntime;
|
|
14
|
+
export declare function getAuthRuntime(): AuthRuntime;
|
|
15
|
+
export declare function tryGetAuthRuntime(): AuthRuntime | undefined;
|
|
16
|
+
export declare function getAuthRuntimeRefreshEndpoint(): string | undefined;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { AccessTokenService } from "../auth/session/access-token.js";
|
|
2
|
+
import { TokenStateMachine } from "../auth/session/token-state.js";
|
|
3
|
+
import { RefreshTokenService } from "../auth/session/refresh-token.js";
|
|
4
|
+
import { KeychainStore } from "../auth/storage/keychain.js";
|
|
5
|
+
import { MemoryTokenStore } from "../auth/storage/memory.js";
|
|
6
|
+
let currentRuntime;
|
|
7
|
+
let currentRefreshEndpoint;
|
|
8
|
+
function deriveBootstrapEndpoint(refreshEndpoint) {
|
|
9
|
+
const url = new URL(refreshEndpoint);
|
|
10
|
+
const parts = url.pathname.split("/").filter(Boolean);
|
|
11
|
+
// Replace the last segment (usually "refresh") with "bootstrap"
|
|
12
|
+
parts.pop();
|
|
13
|
+
parts.push("bootstrap");
|
|
14
|
+
url.pathname = "/" + parts.join("/");
|
|
15
|
+
return url.toString();
|
|
16
|
+
}
|
|
17
|
+
export function initAuthRuntime(config) {
|
|
18
|
+
if (currentRuntime) {
|
|
19
|
+
if (currentRefreshEndpoint && currentRefreshEndpoint !== config.refreshEndpoint) {
|
|
20
|
+
throw new Error("Auth runtime already initialized with a different refresh endpoint");
|
|
21
|
+
}
|
|
22
|
+
return currentRuntime;
|
|
23
|
+
}
|
|
24
|
+
const memoryStore = new MemoryTokenStore();
|
|
25
|
+
const secureStore = new KeychainStore();
|
|
26
|
+
const accessTokenService = new AccessTokenService(memoryStore, secureStore);
|
|
27
|
+
const refreshTokenService = new RefreshTokenService(secureStore);
|
|
28
|
+
const tokenState = new TokenStateMachine(accessTokenService, refreshTokenService, {
|
|
29
|
+
refreshEndpoint: config.refreshEndpoint,
|
|
30
|
+
});
|
|
31
|
+
const bootstrapEndpoint = deriveBootstrapEndpoint(config.refreshEndpoint);
|
|
32
|
+
currentRuntime = {
|
|
33
|
+
accessTokenService,
|
|
34
|
+
refreshTokenService,
|
|
35
|
+
tokenState,
|
|
36
|
+
getBootstrapEndpoint: () => bootstrapEndpoint,
|
|
37
|
+
};
|
|
38
|
+
currentRefreshEndpoint = config.refreshEndpoint;
|
|
39
|
+
return currentRuntime;
|
|
40
|
+
}
|
|
41
|
+
export function getAuthRuntime() {
|
|
42
|
+
if (!currentRuntime) {
|
|
43
|
+
throw new Error("Auth runtime is not initialized");
|
|
44
|
+
}
|
|
45
|
+
return currentRuntime;
|
|
46
|
+
}
|
|
47
|
+
export function tryGetAuthRuntime() {
|
|
48
|
+
return currentRuntime;
|
|
49
|
+
}
|
|
50
|
+
export function getAuthRuntimeRefreshEndpoint() {
|
|
51
|
+
return currentRefreshEndpoint;
|
|
52
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { InvalidInputError } from "./errors.js";
|
|
2
|
+
export function assertDispatchInputStrict(input) {
|
|
3
|
+
if (!input || typeof input !== "object") {
|
|
4
|
+
throw new InvalidInputError("Dispatch input must be an object");
|
|
5
|
+
}
|
|
6
|
+
if (!input.intent || typeof input.intent !== "string") {
|
|
7
|
+
throw new InvalidInputError("Missing dispatch intent");
|
|
8
|
+
}
|
|
9
|
+
if (!input.infraKey || typeof input.infraKey !== "string") {
|
|
10
|
+
throw new InvalidInputError("Missing infraKey");
|
|
11
|
+
}
|
|
12
|
+
if (!input.commandType || typeof input.commandType !== "string") {
|
|
13
|
+
throw new InvalidInputError("Missing commandType");
|
|
14
|
+
}
|
|
15
|
+
if (input.runType !== "infra" && input.runType !== "runtime") {
|
|
16
|
+
throw new InvalidInputError("runType must be 'infra' or 'runtime'");
|
|
17
|
+
}
|
|
18
|
+
if (!input.resourceId || typeof input.resourceId !== "string") {
|
|
19
|
+
throw new InvalidInputError("Missing resourceId");
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { EncryptedPayload } from "../gateway/types.js";
|
|
2
|
+
import { EncryptOptions } from "../types/dispatcher.js";
|
|
3
|
+
/**
|
|
4
|
+
* Encrypt payload using X25519 + HKDF + AES-256-GCM.
|
|
5
|
+
*
|
|
6
|
+
* Security properties:
|
|
7
|
+
* - Ephemeral client key per invocation
|
|
8
|
+
* - Forward secrecy
|
|
9
|
+
* - AEAD (authenticated encryption)
|
|
10
|
+
* - No plaintext on wire
|
|
11
|
+
*/
|
|
12
|
+
export declare function encryptPayload(payload: Record<string, any>, opts: EncryptOptions): EncryptedPayload | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import crypto from "crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Encrypt payload using X25519 + HKDF + AES-256-GCM.
|
|
4
|
+
*
|
|
5
|
+
* Security properties:
|
|
6
|
+
* - Ephemeral client key per invocation
|
|
7
|
+
* - Forward secrecy
|
|
8
|
+
* - AEAD (authenticated encryption)
|
|
9
|
+
* - No plaintext on wire
|
|
10
|
+
*/
|
|
11
|
+
export function encryptPayload(payload, opts) {
|
|
12
|
+
if (!opts?.serverPublicKeyRawB64) {
|
|
13
|
+
// allow caller to retry bootstrap/dispatch instead of breaking flow
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
// Normalize payload into an object we can safely enrich
|
|
17
|
+
let normalized;
|
|
18
|
+
if (payload === null || payload === undefined) {
|
|
19
|
+
normalized = {};
|
|
20
|
+
}
|
|
21
|
+
else if (typeof payload === "object") {
|
|
22
|
+
normalized = payload;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
// Primitive payloads are wrapped
|
|
26
|
+
normalized = { value: payload };
|
|
27
|
+
}
|
|
28
|
+
const created = Date.now();
|
|
29
|
+
const jti = crypto.randomBytes(16).toString("base64url");
|
|
30
|
+
const payloadStr = JSON.stringify(normalized);
|
|
31
|
+
// Load server public key from RAW X25519 bytes (base64)
|
|
32
|
+
const serverRaw = Buffer.from(opts.serverPublicKeyRawB64, "base64");
|
|
33
|
+
if (serverRaw.length !== 32) {
|
|
34
|
+
// invalid key (likely stale bootstrap or encoding mismatch)
|
|
35
|
+
// return null so dispatcher can trigger retry / bootstrap refresh
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Build SPKI DER for X25519 public key and import as KeyObject
|
|
39
|
+
// DER prefix for X25519 SPKI (OID 1.3.101.110) + BIT STRING header
|
|
40
|
+
const x25519SpkiPrefix = Buffer.from("302a300506032b656e032100", "hex");
|
|
41
|
+
const serverSpkiDer = Buffer.concat([x25519SpkiPrefix, serverRaw]);
|
|
42
|
+
const serverPubKey = crypto.createPublicKey({
|
|
43
|
+
key: serverSpkiDer,
|
|
44
|
+
format: "der",
|
|
45
|
+
type: "spki",
|
|
46
|
+
});
|
|
47
|
+
// Generate ephemeral client key pair
|
|
48
|
+
const clientKeyPair = crypto.generateKeyPairSync("x25519");
|
|
49
|
+
const clientPriv = clientKeyPair.privateKey;
|
|
50
|
+
const clientPub = clientKeyPair.publicKey;
|
|
51
|
+
// Derive shared secret
|
|
52
|
+
const sharedSecret = crypto.diffieHellman({
|
|
53
|
+
privateKey: clientPriv,
|
|
54
|
+
publicKey: serverPubKey,
|
|
55
|
+
});
|
|
56
|
+
// Derive symmetric key via HKDF (dynamic salt per message)
|
|
57
|
+
const salt = Buffer.from(`${created}.${jti}`, "utf8");
|
|
58
|
+
const aesKey = Buffer.from(crypto.hkdfSync("sha256", sharedSecret, salt, "viza-ecdh-key", 32));
|
|
59
|
+
// Encrypt using AES-256-GCM
|
|
60
|
+
const iv = crypto.randomBytes(12);
|
|
61
|
+
const cipher = crypto.createCipheriv("aes-256-gcm", aesKey, iv);
|
|
62
|
+
cipher.setAAD(Buffer.from(`${created}.${jti}`));
|
|
63
|
+
const encrypted = Buffer.concat([
|
|
64
|
+
cipher.update(payloadStr, "utf8"),
|
|
65
|
+
cipher.final(),
|
|
66
|
+
]);
|
|
67
|
+
const tag = cipher.getAuthTag();
|
|
68
|
+
const result = {
|
|
69
|
+
alg: "x25519-hkdf-aes256gcm",
|
|
70
|
+
clientPub: clientPub
|
|
71
|
+
.export({ type: "spki", format: "der" })
|
|
72
|
+
.toString("base64"),
|
|
73
|
+
iv: iv.toString("base64"),
|
|
74
|
+
tag: tag.toString("base64"),
|
|
75
|
+
data: encrypted.toString("base64"),
|
|
76
|
+
created,
|
|
77
|
+
jti,
|
|
78
|
+
};
|
|
79
|
+
return result;
|
|
80
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DispatchResult, DispatchInput, DispatchOptions, DispatchHandle, DispatchMode } from "../types/dispatcher.js";
|
|
2
|
+
type EmailOtpFlowHooks = {
|
|
3
|
+
onBeforePrompt?: () => void;
|
|
4
|
+
onAfterPrompt?: () => void;
|
|
5
|
+
};
|
|
6
|
+
export declare function runEmailOtpFlow(input: DispatchInput, options: DispatchOptions, hooks?: EmailOtpFlowHooks, dispatcherCall?: (flowInput: DispatchInput, flowOptions: DispatchOptions, mode?: DispatchMode) => Promise<DispatchHandle>): Promise<DispatchResult>;
|
|
7
|
+
export {};
|