@ankhorage/contracts 2.0.0 → 2.1.0
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/CHANGELOG.md +6 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/secretManifest.d.ts +11 -0
- package/dist/secretManifest.d.ts.map +1 -0
- package/dist/secretManifest.js +2 -0
- package/dist/secretManifest.js.map +1 -0
- package/dist/secrets.d.ts +85 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +83 -0
- package/dist/secrets.js.map +1 -0
- package/package.json +8 -2
- package/src/auth.ts +3 -0
- package/src/index.ts +2 -0
- package/src/secretManifest.ts +12 -0
- package/src/secrets.test.ts +88 -0
- package/src/secrets.ts +188 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @ankhorage/contracts
|
|
2
2
|
|
|
3
|
+
## 2.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 7249c06: Add provider-neutral secret-store contracts, canonical `infra.secretStore` provider selection, logical OAuth `credentialsRef` support, and validation helpers that reject inline secret fields.
|
|
8
|
+
|
|
3
9
|
## 2.0.0
|
|
4
10
|
|
|
5
11
|
### Major Changes
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SecretRef } from './secrets';
|
|
1
2
|
import type { IconSpec } from './types';
|
|
2
3
|
export declare const AUTH_IDENTIFIER_KINDS: readonly ["email", "phone", "username"];
|
|
3
4
|
export type AuthIdentifierKind = (typeof AUTH_IDENTIFIER_KINDS)[number];
|
|
@@ -44,6 +45,8 @@ export interface AuthOAuthProviderConfig {
|
|
|
44
45
|
redirectTo?: string;
|
|
45
46
|
queryParams?: Record<string, string>;
|
|
46
47
|
icon?: IconSpec;
|
|
48
|
+
/** Logical server-side secret reference; raw credentials must never be stored here. */
|
|
49
|
+
credentialsRef?: SecretRef;
|
|
47
50
|
}
|
|
48
51
|
export interface AuthOAuthConfig {
|
|
49
52
|
enabled: boolean;
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAExC,eAAO,MAAM,qBAAqB,yCAA0C,CAAC;AAC7E,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,qBAAqB,CAAC,CAAC,MAAM,CAAC,CAAC;AAExE,eAAO,MAAM,mBAAmB,6FAMtB,CAAC;AACX,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACxE,MAAM,MAAM,eAAe,GAAG,oBAAoB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAEnE,eAAO,MAAM,uBAAuB,8MAoB1B,CAAC;AACX,MAAM,MAAM,wBAAwB,GAAG,CAAC,OAAO,uBAAuB,CAAC,CAAC,MAAM,CAAC,CAAC;AAChF,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC;AAE3E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,eAAO,MAAM,iBAAiB;;;;;;;CAOK,CAAC;AAEpC,wBAAgB,eAAe,CAAC,IAAI,CAAC,EAAE,cAAc,GAAG,cAAc,CAErE;AAED,MAAM,WAAW,gBAAgB;IAC/B,WAAW,EAAE,kBAAkB,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B,cAAc,EAAE,eAAe,EAAE,CAAC;IAClC,cAAc,CAAC,EAAE,eAAe,EAAE,CAAC;CACpC;AAED,MAAM,WAAW,uBAAuB;IACtC,EAAE,EAAE,mBAAmB,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,IAAI,CAAC,EAAE,QAAQ,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAGA,MAAM,CAAC,MAAM,qBAAqB,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,UAAU,CAAU,CAAC;AAG7E,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,GAAG,qBAAqB;IACxB,UAAU;IACV,aAAa;IACb,WAAW;IACX,UAAU;CACF,CAAC;AAIX,MAAM,CAAC,MAAM,uBAAuB,GAAG;IACrC,OAAO;IACP,OAAO;IACP,WAAW;IACX,SAAS;IACT,UAAU;IACV,OAAO;IACP,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,UAAU;IACV,UAAU;IACV,QAAQ;IACR,OAAO;IACP,SAAS;IACT,QAAQ;IACR,SAAS;IACT,QAAQ;IACR,MAAM;CACE,CAAC;AAmBX,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,WAAW,EAAE,SAAS;IACtB,WAAW,EAAE,SAAS;IACtB,YAAY,EAAE,UAAU;IACxB,mBAAmB,EAAE,iBAAiB;IACtC,eAAe,EAAE,GAAG;IACpB,iBAAiB,EAAE,SAAS;CACK,CAAC;AAEpC,MAAM,UAAU,eAAe,CAAC,IAAqB;IACnD,OAAO,EAAE,GAAG,CAAC,IAAI,IAAI,iBAAiB,CAAC,EAAE,CAAC;AAC5C,CAAC","sourcesContent":["import type { SecretRef } from './secrets';\nimport type { IconSpec } from './types';\n\nexport const AUTH_IDENTIFIER_KINDS = ['email', 'phone', 'username'] as const;\nexport type AuthIdentifierKind = (typeof AUTH_IDENTIFIER_KINDS)[number];\n\nexport const AUTH_SIGN_UP_FIELDS = [\n ...AUTH_IDENTIFIER_KINDS,\n 'password',\n 'displayName',\n 'firstName',\n 'lastName',\n] as const;\nexport type KnownAuthSignUpField = (typeof AUTH_SIGN_UP_FIELDS)[number];\nexport type AuthSignUpField = KnownAuthSignUpField | (string & {});\n\nexport const AUTH_OAUTH_PROVIDER_IDS = [\n 'apple',\n 'azure',\n 'bitbucket',\n 'discord',\n 'facebook',\n 'figma',\n 'github',\n 'gitlab',\n 'google',\n 'kakao',\n 'keycloak',\n 'linkedin',\n 'notion',\n 'slack',\n 'spotify',\n 'twitch',\n 'twitter',\n 'workos',\n 'zoom',\n] as const;\nexport type KnownAuthOAuthProviderId = (typeof AUTH_OAUTH_PROVIDER_IDS)[number];\nexport type AuthOAuthProviderId = KnownAuthOAuthProviderId | (string & {});\n\nexport interface AuthIdentifier {\n kind: AuthIdentifierKind;\n value: string;\n}\n\nexport interface AuthFlowConfig {\n signInRoute: string;\n signUpRoute?: string;\n signOutRoute?: string;\n forgotPasswordRoute?: string;\n otpRoute?: string;\n postSignInRoute: string;\n unauthorizedRoute?: string;\n}\n\nexport const DEFAULT_AUTH_FLOW = {\n signInRoute: 'sign-in',\n signUpRoute: 'sign-up',\n signOutRoute: 'sign-out',\n forgotPasswordRoute: 'forgot-password',\n postSignInRoute: '/',\n unauthorizedRoute: 'sign-in',\n} as const satisfies AuthFlowConfig;\n\nexport function resolveAuthFlow(flow?: AuthFlowConfig): AuthFlowConfig {\n return { ...(flow ?? DEFAULT_AUTH_FLOW) };\n}\n\nexport interface AuthSignInConfig {\n identifiers: AuthIdentifierKind[];\n}\n\nexport interface AuthSignUpConfig {\n requiredFields: AuthSignUpField[];\n optionalFields?: AuthSignUpField[];\n}\n\nexport interface AuthOAuthProviderConfig {\n id: AuthOAuthProviderId;\n label?: string;\n enabled?: boolean;\n scopes?: string[];\n redirectTo?: string;\n queryParams?: Record<string, string>;\n icon?: IconSpec;\n /** Logical server-side secret reference; raw credentials must never be stored here. */\n credentialsRef?: SecretRef;\n}\n\nexport interface AuthOAuthConfig {\n enabled: boolean;\n callbackRoute: string;\n providers: AuthOAuthProviderConfig[];\n}\n\nexport interface AuthProviderConfig {\n provider: string;\n signIn: AuthSignInConfig;\n signUp?: AuthSignUpConfig;\n oauth?: AuthOAuthConfig;\n passwordReset?: {\n enabled: boolean;\n };\n otp?: {\n enabled: boolean;\n };\n}\n\nexport interface AuthUser {\n id: string;\n email?: string;\n phone?: string;\n username?: string;\n displayName?: string;\n avatarUrl?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuthSession {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType?: string;\n user: AuthUser;\n}\n\nexport interface AuthAdapterError {\n code: string;\n message: string;\n cause?: unknown;\n}\n\nexport type AuthResult<TData = void> =\n | {\n ok: true;\n data?: TData;\n }\n | {\n ok: false;\n error: AuthAdapterError;\n };\n\nexport interface SignInInput {\n identifier: AuthIdentifier;\n password?: string;\n otp?: string;\n redirectTo?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface SignUpInput {\n identifier: AuthIdentifier;\n password?: string;\n profile?: Record<string, unknown>;\n redirectTo?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface SignOutInput {\n allDevices?: boolean;\n}\n\nexport interface PasswordResetInput {\n identifier: AuthIdentifier;\n redirectTo?: string;\n}\n\nexport interface VerifyOtpInput {\n identifier: AuthIdentifier;\n token: string;\n redirectTo?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface SignInWithOAuthInput {\n provider: AuthOAuthProviderId;\n redirectTo?: string;\n scopes?: string[];\n queryParams?: Record<string, string>;\n metadata?: Record<string, unknown>;\n}\n\nexport interface AuthOAuthRedirect {\n provider: AuthOAuthProviderId;\n url: string;\n}\n\nexport interface CompleteOAuthSignInInput {\n url: string;\n redirectTo?: string;\n}\n\nexport interface AuthAdapterCapabilities {\n signInIdentifiers: AuthIdentifierKind[];\n supportsSignUp: boolean;\n supportsPasswordReset: boolean;\n supportsOtp: boolean;\n supportsSessionRefresh: boolean;\n supportsOAuth?: boolean;\n oauthProviders?: AuthOAuthProviderId[];\n}\n\nexport interface AuthAdapter {\n readonly capabilities?: AuthAdapterCapabilities;\n\n signIn(input: SignInInput): Promise<AuthResult<AuthSession>>;\n signUp(input: SignUpInput): Promise<AuthResult<AuthSession | AuthUser>>;\n signOut(input?: SignOutInput): Promise<AuthResult>;\n\n getSession(): Promise<AuthResult<AuthSession | null>>;\n refreshSession?(): Promise<AuthResult<AuthSession | null>>;\n\n requestPasswordReset?(input: PasswordResetInput): Promise<AuthResult>;\n verifyOtp?(input: VerifyOtpInput): Promise<AuthResult<AuthSession>>;\n\n signInWithOAuth?(input: SignInWithOAuthInput): Promise<AuthResult<AuthOAuthRedirect>>;\n completeOAuthSignIn?(input: CompleteOAuthSignInInput): Promise<AuthResult<AuthSession>>;\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * from './db';
|
|
|
6
6
|
export * from './nutrition';
|
|
7
7
|
export * from './requirements';
|
|
8
8
|
export * from './runtimeCallbacks';
|
|
9
|
+
export * from './secretManifest';
|
|
10
|
+
export * from './secrets';
|
|
9
11
|
export * from './state';
|
|
10
12
|
export * from './storage';
|
|
11
13
|
export * from './types';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -6,6 +6,8 @@ export * from './db';
|
|
|
6
6
|
export * from './nutrition';
|
|
7
7
|
export * from './requirements';
|
|
8
8
|
export * from './runtimeCallbacks';
|
|
9
|
+
export * from './secretManifest';
|
|
10
|
+
export * from './secrets';
|
|
9
11
|
export * from './state';
|
|
10
12
|
export * from './storage';
|
|
11
13
|
export * from './types';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC","sourcesContent":["export * from './auth';\nexport * from './bindings';\nexport * from './cli';\nexport * from './data';\nexport * from './db';\nexport * from './nutrition';\nexport * from './requirements';\nexport * from './runtimeCallbacks';\nexport * from './state';\nexport * from './storage';\nexport * from './types';\nexport * from './ui';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,YAAY,CAAC;AAC3B,cAAc,OAAO,CAAC;AACtB,cAAc,QAAQ,CAAC;AACvB,cAAc,MAAM,CAAC;AACrB,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,WAAW,CAAC;AAC1B,cAAc,SAAS,CAAC;AACxB,cAAc,MAAM,CAAC","sourcesContent":["export * from './auth';\nexport * from './bindings';\nexport * from './cli';\nexport * from './data';\nexport * from './db';\nexport * from './nutrition';\nexport * from './requirements';\nexport * from './runtimeCallbacks';\nexport * from './secretManifest';\nexport * from './secrets';\nexport * from './state';\nexport * from './storage';\nexport * from './types';\nexport * from './ui';\n"]}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { SecretStoreProvider } from './secrets';
|
|
2
|
+
export interface InfraSecretStoreSpec {
|
|
3
|
+
provider: SecretStoreProvider;
|
|
4
|
+
}
|
|
5
|
+
declare module './types' {
|
|
6
|
+
interface InfraManifest {
|
|
7
|
+
/** Non-secret provider selection. Bootstrap credentials stay in trusted environment config. */
|
|
8
|
+
secretStore?: InfraSecretStoreSpec;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=secretManifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretManifest.d.ts","sourceRoot":"","sources":["../src/secretManifest.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,mBAAmB,CAAC;CAC/B;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,aAAa;QACrB,+FAA+F;QAC/F,WAAW,CAAC,EAAE,oBAAoB,CAAC;KACpC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretManifest.js","sourceRoot":"","sources":["../src/secretManifest.ts"],"names":[],"mappings":"","sourcesContent":["import type { SecretStoreProvider } from './secrets';\n\nexport interface InfraSecretStoreSpec {\n provider: SecretStoreProvider;\n}\n\ndeclare module './types' {\n interface InfraManifest {\n /** Non-secret provider selection. Bootstrap credentials stay in trusted environment config. */\n secretStore?: InfraSecretStoreSpec;\n }\n}\n"]}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
export declare const SECRET_STORE_PROVIDERS: readonly ["supabase-vault"];
|
|
2
|
+
export type KnownSecretStoreProvider = (typeof SECRET_STORE_PROVIDERS)[number];
|
|
3
|
+
export type SecretStoreProvider = KnownSecretStoreProvider | (string & {});
|
|
4
|
+
export type SecretRef = string;
|
|
5
|
+
export interface SecretScope {
|
|
6
|
+
projectId: string;
|
|
7
|
+
environment: string;
|
|
8
|
+
}
|
|
9
|
+
export type SecretPayload = Readonly<Record<string, string>>;
|
|
10
|
+
export interface SecretMetadata {
|
|
11
|
+
ref: SecretRef;
|
|
12
|
+
scope: SecretScope;
|
|
13
|
+
kind: string;
|
|
14
|
+
provider?: string;
|
|
15
|
+
configuredFields: readonly string[];
|
|
16
|
+
createdAt: string;
|
|
17
|
+
updatedAt: string;
|
|
18
|
+
}
|
|
19
|
+
export declare const SECRET_STORE_ERROR_CODES: readonly ["invalid_config", "invalid_reference", "invalid_payload", "not_found", "conflict", "permission_denied", "unavailable", "provider_error"];
|
|
20
|
+
export type SecretStoreErrorCode = (typeof SECRET_STORE_ERROR_CODES)[number];
|
|
21
|
+
export interface SecretStoreError {
|
|
22
|
+
code: SecretStoreErrorCode;
|
|
23
|
+
message: string;
|
|
24
|
+
cause?: unknown;
|
|
25
|
+
}
|
|
26
|
+
export type SecretStoreOkResult<TData> = [TData] extends [void] ? {
|
|
27
|
+
ok: true;
|
|
28
|
+
data?: undefined;
|
|
29
|
+
} : {
|
|
30
|
+
ok: true;
|
|
31
|
+
data: TData;
|
|
32
|
+
};
|
|
33
|
+
export type SecretStoreResult<TData = void> = SecretStoreOkResult<TData> | {
|
|
34
|
+
ok: false;
|
|
35
|
+
error: SecretStoreError;
|
|
36
|
+
};
|
|
37
|
+
export interface SecretListInput {
|
|
38
|
+
scope: SecretScope;
|
|
39
|
+
kind?: string;
|
|
40
|
+
provider?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface SecretGetMetadataInput {
|
|
43
|
+
scope: SecretScope;
|
|
44
|
+
ref: SecretRef;
|
|
45
|
+
}
|
|
46
|
+
export interface SecretCreateInput {
|
|
47
|
+
scope: SecretScope;
|
|
48
|
+
ref: SecretRef;
|
|
49
|
+
kind: string;
|
|
50
|
+
provider?: string;
|
|
51
|
+
payload: SecretPayload;
|
|
52
|
+
}
|
|
53
|
+
export interface SecretReplaceInput {
|
|
54
|
+
scope: SecretScope;
|
|
55
|
+
ref: SecretRef;
|
|
56
|
+
payload: SecretPayload;
|
|
57
|
+
}
|
|
58
|
+
export interface SecretRemoveInput {
|
|
59
|
+
scope: SecretScope;
|
|
60
|
+
ref: SecretRef;
|
|
61
|
+
}
|
|
62
|
+
export interface SecretResolveInput {
|
|
63
|
+
scope: SecretScope;
|
|
64
|
+
ref: SecretRef;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Provider-neutral server-side secret-store boundary.
|
|
68
|
+
*
|
|
69
|
+
* `resolve` is for trusted server/deployment code only. Browser bridges must expose
|
|
70
|
+
* metadata operations without forwarding raw secret payloads.
|
|
71
|
+
*/
|
|
72
|
+
export interface SecretStoreAdapter {
|
|
73
|
+
list(input: SecretListInput): Promise<SecretStoreResult<readonly SecretMetadata[]>>;
|
|
74
|
+
getMetadata(input: SecretGetMetadataInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
75
|
+
create(input: SecretCreateInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
76
|
+
replace(input: SecretReplaceInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
77
|
+
remove(input: SecretRemoveInput): Promise<SecretStoreResult>;
|
|
78
|
+
resolve(input: SecretResolveInput): Promise<SecretStoreResult<SecretPayload>>;
|
|
79
|
+
}
|
|
80
|
+
export declare function normalizeSecretRef(value: string): SecretStoreResult<SecretRef>;
|
|
81
|
+
export declare function normalizeSecretScope(scope: SecretScope): SecretStoreResult<SecretScope>;
|
|
82
|
+
export declare function validateSecretPayload(payload: SecretPayload): SecretStoreResult<SecretPayload>;
|
|
83
|
+
export declare const FORBIDDEN_INLINE_SECRET_FIELDS: readonly ["apiKey", "clientSecret", "databasePassword", "privateKey", "serviceRoleKey", "token"];
|
|
84
|
+
export declare function findForbiddenInlineSecretFields(value: unknown): readonly string[];
|
|
85
|
+
//# sourceMappingURL=secrets.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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"}
|
package/dist/secrets.js
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
export const SECRET_STORE_PROVIDERS = ['supabase-vault'];
|
|
2
|
+
export const SECRET_STORE_ERROR_CODES = [
|
|
3
|
+
'invalid_config',
|
|
4
|
+
'invalid_reference',
|
|
5
|
+
'invalid_payload',
|
|
6
|
+
'not_found',
|
|
7
|
+
'conflict',
|
|
8
|
+
'permission_denied',
|
|
9
|
+
'unavailable',
|
|
10
|
+
'provider_error',
|
|
11
|
+
];
|
|
12
|
+
const SECRET_REF_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
|
|
13
|
+
export function normalizeSecretRef(value) {
|
|
14
|
+
const normalized = value
|
|
15
|
+
.trim()
|
|
16
|
+
.replace(/^\/+|\/+$/g, '')
|
|
17
|
+
.replace(/\/{2,}/g, '/');
|
|
18
|
+
if (normalized.length === 0 ||
|
|
19
|
+
normalized.length > 255 ||
|
|
20
|
+
normalized.split('/').some((segment) => !SECRET_REF_SEGMENT_PATTERN.test(segment))) {
|
|
21
|
+
return {
|
|
22
|
+
ok: false,
|
|
23
|
+
error: {
|
|
24
|
+
code: 'invalid_reference',
|
|
25
|
+
message: 'Secret reference must contain lowercase path segments using letters, numbers, dots, underscores, or hyphens.',
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { ok: true, data: normalized };
|
|
30
|
+
}
|
|
31
|
+
export function normalizeSecretScope(scope) {
|
|
32
|
+
const projectId = scope.projectId.trim();
|
|
33
|
+
const environment = scope.environment.trim();
|
|
34
|
+
if (projectId.length === 0 || environment.length === 0) {
|
|
35
|
+
return {
|
|
36
|
+
ok: false,
|
|
37
|
+
error: {
|
|
38
|
+
code: 'invalid_config',
|
|
39
|
+
message: 'Secret scope requires non-empty projectId and environment values.',
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return { ok: true, data: { projectId, environment } };
|
|
44
|
+
}
|
|
45
|
+
export function validateSecretPayload(payload) {
|
|
46
|
+
const entries = Object.entries(payload);
|
|
47
|
+
if (entries.length === 0) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
error: {
|
|
51
|
+
code: 'invalid_payload',
|
|
52
|
+
message: 'Secret payload must contain at least one field.',
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
for (const [field, value] of entries) {
|
|
57
|
+
if (field.trim().length === 0 || typeof value !== 'string' || value.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
ok: false,
|
|
60
|
+
error: {
|
|
61
|
+
code: 'invalid_payload',
|
|
62
|
+
message: `Secret payload field ${field.trim().length === 0 ? '<empty>' : field} must contain a non-empty string value.`,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return { ok: true, data: Object.freeze(Object.fromEntries(entries)) };
|
|
68
|
+
}
|
|
69
|
+
export const FORBIDDEN_INLINE_SECRET_FIELDS = [
|
|
70
|
+
'apiKey',
|
|
71
|
+
'clientSecret',
|
|
72
|
+
'databasePassword',
|
|
73
|
+
'privateKey',
|
|
74
|
+
'serviceRoleKey',
|
|
75
|
+
'token',
|
|
76
|
+
];
|
|
77
|
+
export function findForbiddenInlineSecretFields(value) {
|
|
78
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
79
|
+
return [];
|
|
80
|
+
const record = value;
|
|
81
|
+
return FORBIDDEN_INLINE_SECRET_FIELDS.filter((field) => field in record);
|
|
82
|
+
}
|
|
83
|
+
//# sourceMappingURL=secrets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"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","sourcesContent":["export const SECRET_STORE_PROVIDERS = ['supabase-vault'] as const;\nexport type KnownSecretStoreProvider = (typeof SECRET_STORE_PROVIDERS)[number];\nexport type SecretStoreProvider = KnownSecretStoreProvider | (string & {});\n\nexport type SecretRef = string;\n\nexport interface SecretScope {\n projectId: string;\n environment: string;\n}\n\nexport type SecretPayload = Readonly<Record<string, string>>;\n\nexport interface SecretMetadata {\n ref: SecretRef;\n scope: SecretScope;\n kind: string;\n provider?: string;\n configuredFields: readonly string[];\n createdAt: string;\n updatedAt: string;\n}\n\nexport const SECRET_STORE_ERROR_CODES = [\n 'invalid_config',\n 'invalid_reference',\n 'invalid_payload',\n 'not_found',\n 'conflict',\n 'permission_denied',\n 'unavailable',\n 'provider_error',\n] as const;\nexport type SecretStoreErrorCode = (typeof SECRET_STORE_ERROR_CODES)[number];\n\nexport interface SecretStoreError {\n code: SecretStoreErrorCode;\n message: string;\n cause?: unknown;\n}\n\nexport type SecretStoreOkResult<TData> = [TData] extends [void]\n ? { ok: true; data?: undefined }\n : { ok: true; data: TData };\n\nexport type SecretStoreResult<TData = void> =\n | SecretStoreOkResult<TData>\n | {\n ok: false;\n error: SecretStoreError;\n };\n\nexport interface SecretListInput {\n scope: SecretScope;\n kind?: string;\n provider?: string;\n}\n\nexport interface SecretGetMetadataInput {\n scope: SecretScope;\n ref: SecretRef;\n}\n\nexport interface SecretCreateInput {\n scope: SecretScope;\n ref: SecretRef;\n kind: string;\n provider?: string;\n payload: SecretPayload;\n}\n\nexport interface SecretReplaceInput {\n scope: SecretScope;\n ref: SecretRef;\n payload: SecretPayload;\n}\n\nexport interface SecretRemoveInput {\n scope: SecretScope;\n ref: SecretRef;\n}\n\nexport interface SecretResolveInput {\n scope: SecretScope;\n ref: SecretRef;\n}\n\n/**\n * Provider-neutral server-side secret-store boundary.\n *\n * `resolve` is for trusted server/deployment code only. Browser bridges must expose\n * metadata operations without forwarding raw secret payloads.\n */\nexport interface SecretStoreAdapter {\n list(input: SecretListInput): Promise<SecretStoreResult<readonly SecretMetadata[]>>;\n getMetadata(input: SecretGetMetadataInput): Promise<SecretStoreResult<SecretMetadata>>;\n create(input: SecretCreateInput): Promise<SecretStoreResult<SecretMetadata>>;\n replace(input: SecretReplaceInput): Promise<SecretStoreResult<SecretMetadata>>;\n remove(input: SecretRemoveInput): Promise<SecretStoreResult>;\n resolve(input: SecretResolveInput): Promise<SecretStoreResult<SecretPayload>>;\n}\n\nconst SECRET_REF_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;\n\nexport function normalizeSecretRef(value: string): SecretStoreResult<SecretRef> {\n const normalized = value\n .trim()\n .replace(/^\\/+|\\/+$/g, '')\n .replace(/\\/{2,}/g, '/');\n\n if (\n normalized.length === 0 ||\n normalized.length > 255 ||\n normalized.split('/').some((segment) => !SECRET_REF_SEGMENT_PATTERN.test(segment))\n ) {\n return {\n ok: false,\n error: {\n code: 'invalid_reference',\n message:\n 'Secret reference must contain lowercase path segments using letters, numbers, dots, underscores, or hyphens.',\n },\n };\n }\n\n return { ok: true, data: normalized };\n}\n\nexport function normalizeSecretScope(scope: SecretScope): SecretStoreResult<SecretScope> {\n const projectId = scope.projectId.trim();\n const environment = scope.environment.trim();\n\n if (projectId.length === 0 || environment.length === 0) {\n return {\n ok: false,\n error: {\n code: 'invalid_config',\n message: 'Secret scope requires non-empty projectId and environment values.',\n },\n };\n }\n\n return { ok: true, data: { projectId, environment } };\n}\n\nexport function validateSecretPayload(payload: SecretPayload): SecretStoreResult<SecretPayload> {\n const entries = Object.entries(payload);\n\n if (entries.length === 0) {\n return {\n ok: false,\n error: {\n code: 'invalid_payload',\n message: 'Secret payload must contain at least one field.',\n },\n };\n }\n\n for (const [field, value] of entries) {\n if (field.trim().length === 0 || typeof value !== 'string' || value.length === 0) {\n return {\n ok: false,\n error: {\n code: 'invalid_payload',\n message: `Secret payload field ${field.trim().length === 0 ? '<empty>' : field} must contain a non-empty string value.`,\n },\n };\n }\n }\n\n return { ok: true, data: Object.freeze(Object.fromEntries(entries)) };\n}\n\nexport const FORBIDDEN_INLINE_SECRET_FIELDS = [\n 'apiKey',\n 'clientSecret',\n 'databasePassword',\n 'privateKey',\n 'serviceRoleKey',\n 'token',\n] as const;\n\nexport function findForbiddenInlineSecretFields(value: unknown): readonly string[] {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) return [];\n\n const record = value as Record<string, unknown>;\n return FORBIDDEN_INLINE_SECRET_FIELDS.filter((field) => field in record);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/contracts",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"ankh": {
|
|
6
6
|
"category": "contracts",
|
|
@@ -53,6 +53,10 @@
|
|
|
53
53
|
"types": "./dist/runtimeCallbacks.d.ts",
|
|
54
54
|
"default": "./dist/runtimeCallbacks.js"
|
|
55
55
|
},
|
|
56
|
+
"./secrets": {
|
|
57
|
+
"types": "./dist/secrets.d.ts",
|
|
58
|
+
"default": "./dist/secrets.js"
|
|
59
|
+
},
|
|
56
60
|
"./state": {
|
|
57
61
|
"types": "./dist/state.d.ts",
|
|
58
62
|
"default": "./dist/state.js"
|
|
@@ -66,13 +70,15 @@
|
|
|
66
70
|
"default": "./dist/ui.js"
|
|
67
71
|
}
|
|
68
72
|
},
|
|
69
|
-
"description": "Serializable app, action, and
|
|
73
|
+
"description": "Serializable app, action, theme, auth, and secret-store contracts for Ankhorage.",
|
|
70
74
|
"keywords": [
|
|
71
75
|
"typescript",
|
|
72
76
|
"contracts",
|
|
73
77
|
"schema",
|
|
74
78
|
"manifest",
|
|
75
79
|
"auth",
|
|
80
|
+
"secrets",
|
|
81
|
+
"secret-store",
|
|
76
82
|
"storage",
|
|
77
83
|
"adapter"
|
|
78
84
|
],
|
package/src/auth.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { SecretRef } from './secrets';
|
|
1
2
|
import type { IconSpec } from './types';
|
|
2
3
|
|
|
3
4
|
export const AUTH_IDENTIFIER_KINDS = ['email', 'phone', 'username'] as const;
|
|
@@ -82,6 +83,8 @@ export interface AuthOAuthProviderConfig {
|
|
|
82
83
|
redirectTo?: string;
|
|
83
84
|
queryParams?: Record<string, string>;
|
|
84
85
|
icon?: IconSpec;
|
|
86
|
+
/** Logical server-side secret reference; raw credentials must never be stored here. */
|
|
87
|
+
credentialsRef?: SecretRef;
|
|
85
88
|
}
|
|
86
89
|
|
|
87
90
|
export interface AuthOAuthConfig {
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * from './db';
|
|
|
6
6
|
export * from './nutrition';
|
|
7
7
|
export * from './requirements';
|
|
8
8
|
export * from './runtimeCallbacks';
|
|
9
|
+
export * from './secretManifest';
|
|
10
|
+
export * from './secrets';
|
|
9
11
|
export * from './state';
|
|
10
12
|
export * from './storage';
|
|
11
13
|
export * from './types';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SecretStoreProvider } from './secrets';
|
|
2
|
+
|
|
3
|
+
export interface InfraSecretStoreSpec {
|
|
4
|
+
provider: SecretStoreProvider;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
declare module './types' {
|
|
8
|
+
interface InfraManifest {
|
|
9
|
+
/** Non-secret provider selection. Bootstrap credentials stay in trusted environment config. */
|
|
10
|
+
secretStore?: InfraSecretStoreSpec;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import './secretManifest';
|
|
2
|
+
|
|
3
|
+
import { describe, expect, test } from 'bun:test';
|
|
4
|
+
|
|
5
|
+
import type { AuthOAuthProviderConfig } from './auth';
|
|
6
|
+
import {
|
|
7
|
+
findForbiddenInlineSecretFields,
|
|
8
|
+
normalizeSecretRef,
|
|
9
|
+
normalizeSecretScope,
|
|
10
|
+
validateSecretPayload,
|
|
11
|
+
} from './secrets';
|
|
12
|
+
import type { InfraManifest } from './types';
|
|
13
|
+
|
|
14
|
+
describe('secret-store contracts', () => {
|
|
15
|
+
test('normalizes logical secret references', () => {
|
|
16
|
+
expect(normalizeSecretRef('/auth//oauth/google/')).toEqual({
|
|
17
|
+
ok: true,
|
|
18
|
+
data: 'auth/oauth/google',
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
test('rejects invalid secret references', () => {
|
|
23
|
+
expect(normalizeSecretRef('Auth OAuth/Google')).toEqual({
|
|
24
|
+
ok: false,
|
|
25
|
+
error: {
|
|
26
|
+
code: 'invalid_reference',
|
|
27
|
+
message:
|
|
28
|
+
'Secret reference must contain lowercase path segments using letters, numbers, dots, underscores, or hyphens.',
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('normalizes project and environment scope', () => {
|
|
34
|
+
expect(normalizeSecretScope({ projectId: ' scanner ', environment: ' local ' })).toEqual({
|
|
35
|
+
ok: true,
|
|
36
|
+
data: { projectId: 'scanner', environment: 'local' },
|
|
37
|
+
});
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('validates non-empty string payloads without exposing values', () => {
|
|
41
|
+
expect(validateSecretPayload({ clientId: 'id', clientSecret: 'secret' })).toEqual({
|
|
42
|
+
ok: true,
|
|
43
|
+
data: { clientId: 'id', clientSecret: 'secret' },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const result = validateSecretPayload({ clientSecret: '' });
|
|
47
|
+
expect(result.ok).toBe(false);
|
|
48
|
+
if (!result.ok) {
|
|
49
|
+
expect(result.error.code).toBe('invalid_payload');
|
|
50
|
+
expect(result.error.message).not.toContain('secret-value');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('detects forbidden inline secret fields in manifest-shaped provider config', () => {
|
|
55
|
+
expect(
|
|
56
|
+
findForbiddenInlineSecretFields({
|
|
57
|
+
id: 'google',
|
|
58
|
+
credentialsRef: 'auth/oauth/google',
|
|
59
|
+
clientSecret: 'sentinel-secret-value',
|
|
60
|
+
}),
|
|
61
|
+
).toEqual(['clientSecret']);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test('supports OAuth credential references and canonical infra provider selection', () => {
|
|
65
|
+
const provider: AuthOAuthProviderConfig = {
|
|
66
|
+
id: 'google',
|
|
67
|
+
enabled: true,
|
|
68
|
+
credentialsRef: 'auth/oauth/google',
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
const infra: InfraManifest = {
|
|
72
|
+
plugins: [],
|
|
73
|
+
secretStore: { provider: 'supabase-vault' },
|
|
74
|
+
auth: {
|
|
75
|
+
scope: 'global',
|
|
76
|
+
provider: 'supabase',
|
|
77
|
+
oauth: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
callbackRoute: '/auth/callback',
|
|
80
|
+
providers: [provider],
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
expect(infra.secretStore?.provider).toBe('supabase-vault');
|
|
86
|
+
expect(infra.auth?.oauth?.providers[0]?.credentialsRef).toBe('auth/oauth/google');
|
|
87
|
+
});
|
|
88
|
+
});
|
package/src/secrets.ts
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
export const SECRET_STORE_PROVIDERS = ['supabase-vault'] as const;
|
|
2
|
+
export type KnownSecretStoreProvider = (typeof SECRET_STORE_PROVIDERS)[number];
|
|
3
|
+
export type SecretStoreProvider = KnownSecretStoreProvider | (string & {});
|
|
4
|
+
|
|
5
|
+
export type SecretRef = string;
|
|
6
|
+
|
|
7
|
+
export interface SecretScope {
|
|
8
|
+
projectId: string;
|
|
9
|
+
environment: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export type SecretPayload = Readonly<Record<string, string>>;
|
|
13
|
+
|
|
14
|
+
export interface SecretMetadata {
|
|
15
|
+
ref: SecretRef;
|
|
16
|
+
scope: SecretScope;
|
|
17
|
+
kind: string;
|
|
18
|
+
provider?: string;
|
|
19
|
+
configuredFields: readonly string[];
|
|
20
|
+
createdAt: string;
|
|
21
|
+
updatedAt: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const SECRET_STORE_ERROR_CODES = [
|
|
25
|
+
'invalid_config',
|
|
26
|
+
'invalid_reference',
|
|
27
|
+
'invalid_payload',
|
|
28
|
+
'not_found',
|
|
29
|
+
'conflict',
|
|
30
|
+
'permission_denied',
|
|
31
|
+
'unavailable',
|
|
32
|
+
'provider_error',
|
|
33
|
+
] as const;
|
|
34
|
+
export type SecretStoreErrorCode = (typeof SECRET_STORE_ERROR_CODES)[number];
|
|
35
|
+
|
|
36
|
+
export interface SecretStoreError {
|
|
37
|
+
code: SecretStoreErrorCode;
|
|
38
|
+
message: string;
|
|
39
|
+
cause?: unknown;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export type SecretStoreOkResult<TData> = [TData] extends [void]
|
|
43
|
+
? { ok: true; data?: undefined }
|
|
44
|
+
: { ok: true; data: TData };
|
|
45
|
+
|
|
46
|
+
export type SecretStoreResult<TData = void> =
|
|
47
|
+
| SecretStoreOkResult<TData>
|
|
48
|
+
| {
|
|
49
|
+
ok: false;
|
|
50
|
+
error: SecretStoreError;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
export interface SecretListInput {
|
|
54
|
+
scope: SecretScope;
|
|
55
|
+
kind?: string;
|
|
56
|
+
provider?: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export interface SecretGetMetadataInput {
|
|
60
|
+
scope: SecretScope;
|
|
61
|
+
ref: SecretRef;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface SecretCreateInput {
|
|
65
|
+
scope: SecretScope;
|
|
66
|
+
ref: SecretRef;
|
|
67
|
+
kind: string;
|
|
68
|
+
provider?: string;
|
|
69
|
+
payload: SecretPayload;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface SecretReplaceInput {
|
|
73
|
+
scope: SecretScope;
|
|
74
|
+
ref: SecretRef;
|
|
75
|
+
payload: SecretPayload;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface SecretRemoveInput {
|
|
79
|
+
scope: SecretScope;
|
|
80
|
+
ref: SecretRef;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export interface SecretResolveInput {
|
|
84
|
+
scope: SecretScope;
|
|
85
|
+
ref: SecretRef;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Provider-neutral server-side secret-store boundary.
|
|
90
|
+
*
|
|
91
|
+
* `resolve` is for trusted server/deployment code only. Browser bridges must expose
|
|
92
|
+
* metadata operations without forwarding raw secret payloads.
|
|
93
|
+
*/
|
|
94
|
+
export interface SecretStoreAdapter {
|
|
95
|
+
list(input: SecretListInput): Promise<SecretStoreResult<readonly SecretMetadata[]>>;
|
|
96
|
+
getMetadata(input: SecretGetMetadataInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
97
|
+
create(input: SecretCreateInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
98
|
+
replace(input: SecretReplaceInput): Promise<SecretStoreResult<SecretMetadata>>;
|
|
99
|
+
remove(input: SecretRemoveInput): Promise<SecretStoreResult>;
|
|
100
|
+
resolve(input: SecretResolveInput): Promise<SecretStoreResult<SecretPayload>>;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const SECRET_REF_SEGMENT_PATTERN = /^[a-z0-9](?:[a-z0-9._-]*[a-z0-9])?$/;
|
|
104
|
+
|
|
105
|
+
export function normalizeSecretRef(value: string): SecretStoreResult<SecretRef> {
|
|
106
|
+
const normalized = value
|
|
107
|
+
.trim()
|
|
108
|
+
.replace(/^\/+|\/+$/g, '')
|
|
109
|
+
.replace(/\/{2,}/g, '/');
|
|
110
|
+
|
|
111
|
+
if (
|
|
112
|
+
normalized.length === 0 ||
|
|
113
|
+
normalized.length > 255 ||
|
|
114
|
+
normalized.split('/').some((segment) => !SECRET_REF_SEGMENT_PATTERN.test(segment))
|
|
115
|
+
) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
error: {
|
|
119
|
+
code: 'invalid_reference',
|
|
120
|
+
message:
|
|
121
|
+
'Secret reference must contain lowercase path segments using letters, numbers, dots, underscores, or hyphens.',
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return { ok: true, data: normalized };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export function normalizeSecretScope(scope: SecretScope): SecretStoreResult<SecretScope> {
|
|
130
|
+
const projectId = scope.projectId.trim();
|
|
131
|
+
const environment = scope.environment.trim();
|
|
132
|
+
|
|
133
|
+
if (projectId.length === 0 || environment.length === 0) {
|
|
134
|
+
return {
|
|
135
|
+
ok: false,
|
|
136
|
+
error: {
|
|
137
|
+
code: 'invalid_config',
|
|
138
|
+
message: 'Secret scope requires non-empty projectId and environment values.',
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return { ok: true, data: { projectId, environment } };
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function validateSecretPayload(payload: SecretPayload): SecretStoreResult<SecretPayload> {
|
|
147
|
+
const entries = Object.entries(payload);
|
|
148
|
+
|
|
149
|
+
if (entries.length === 0) {
|
|
150
|
+
return {
|
|
151
|
+
ok: false,
|
|
152
|
+
error: {
|
|
153
|
+
code: 'invalid_payload',
|
|
154
|
+
message: 'Secret payload must contain at least one field.',
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
for (const [field, value] of entries) {
|
|
160
|
+
if (field.trim().length === 0 || typeof value !== 'string' || value.length === 0) {
|
|
161
|
+
return {
|
|
162
|
+
ok: false,
|
|
163
|
+
error: {
|
|
164
|
+
code: 'invalid_payload',
|
|
165
|
+
message: `Secret payload field ${field.trim().length === 0 ? '<empty>' : field} must contain a non-empty string value.`,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { ok: true, data: Object.freeze(Object.fromEntries(entries)) };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export const FORBIDDEN_INLINE_SECRET_FIELDS = [
|
|
175
|
+
'apiKey',
|
|
176
|
+
'clientSecret',
|
|
177
|
+
'databasePassword',
|
|
178
|
+
'privateKey',
|
|
179
|
+
'serviceRoleKey',
|
|
180
|
+
'token',
|
|
181
|
+
] as const;
|
|
182
|
+
|
|
183
|
+
export function findForbiddenInlineSecretFields(value: unknown): readonly string[] {
|
|
184
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value)) return [];
|
|
185
|
+
|
|
186
|
+
const record = value as Record<string, unknown>;
|
|
187
|
+
return FORBIDDEN_INLINE_SECRET_FIELDS.filter((field) => field in record);
|
|
188
|
+
}
|