@getstrata/core 1.0.9 → 1.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 +48 -0
- package/dist/core/auth/authContext.d.ts +5 -2
- package/dist/core/auth/emailVerification.d.ts +1 -1
- package/dist/core/auth/guard.d.ts +5 -1
- package/dist/core/auth/jwtGuard.d.ts +6 -2
- package/dist/core/auth/oauth/oidcIdToken.d.ts +19 -0
- package/dist/core/auth/oauth/oidcProvider.d.ts +16 -4
- package/dist/core/auth/oauth/samlProvider.d.ts +7 -4
- package/dist/core/auth/oneTimeToken.d.ts +19 -0
- package/dist/core/auth/passwordLogin.d.ts +20 -0
- package/dist/core/auth/saml/samlAssertionReplay.d.ts +20 -0
- package/dist/core/auth/saml/samlServiceProvider.d.ts +51 -0
- package/dist/core/contracts/authUserDirectory.d.ts +2 -3
- package/dist/core/database/errors.d.ts +2 -1
- package/dist/core/http/response.d.ts +3 -1
- package/dist/core/mail/mailer.d.ts +6 -1
- package/dist/core/security/oauthState.d.ts +6 -2
- package/dist/core/security/safeFetch.d.ts +1 -0
- package/dist/core/security/safePath.d.ts +4 -0
- package/dist/core/security/safeUrl.d.ts +14 -1
- package/dist/core/security/totp.d.ts +1 -1
- package/dist/core/tenant/databaseTenantContext.d.ts +2 -1
- package/dist/core/tenant/enableTenantRls.d.ts +6 -0
- package/dist/core/tenant/postgresAppRole.d.ts +75 -0
- package/dist/entries/audit/exportAuditLogs.js +223 -40
- package/dist/entries/auth/accessControl.js +1 -0
- package/dist/entries/auth/basicAuthGuard.js +638 -17
- package/dist/entries/auth/emailVerification.js +1 -1
- package/dist/entries/auth/intendedUrlCookie.js +32 -2
- package/dist/entries/auth/jwt.js +1 -1
- package/dist/entries/auth/jwtGuard.js +192 -3
- package/dist/entries/auth/membershipMiddleware.js +1 -0
- package/dist/entries/auth/membershipScope.js +1 -0
- package/dist/entries/auth/membershipService.js +1 -0
- package/dist/entries/auth/oauth/oidcProvider.js +542 -20
- package/dist/entries/auth/oauth/providers.js +313 -5
- package/dist/entries/auth/oauth/samlProvider.js +552 -74
- package/dist/entries/auth/oneTimeToken.js +362 -0
- package/dist/entries/auth/password.js +1 -1
- package/dist/entries/auth/passwordLogin.js +548 -0
- package/dist/entries/auth/policy.js +1 -0
- package/dist/entries/auth/saml/samlServiceProvider.js +571 -0
- package/dist/entries/auth/scimAuthMiddleware.js +10 -3
- package/dist/entries/auth/sessionCookie.js +1 -1
- package/dist/entries/auth/sessionGuard.js +1 -1
- package/dist/entries/auth/tokenHash.js +2 -6
- package/dist/entries/crypto/fieldEncryption.js +1 -1
- package/dist/entries/crypto/mfaSecret.js +31 -5
- package/dist/entries/database/errors.js +29 -12
- package/dist/entries/database/model.js +9 -9
- package/dist/entries/facades.js +72 -9
- package/dist/entries/http/authMiddleware.js +7 -14
- package/dist/entries/http/corsMiddleware.js +69 -2
- package/dist/entries/http/csrfMiddleware.js +33 -43
- package/dist/entries/http/csrfToken.js +2 -2
- package/dist/entries/http/flashMiddleware.js +4 -2
- package/dist/entries/http/flashSession.js +4 -2
- package/dist/entries/http/formRequest.js +2 -2
- package/dist/entries/http/memoryThrottleMiddleware.js +63 -0
- package/dist/entries/http/pagination.js +2 -2
- package/dist/entries/http/requirePasswordConfirmMiddleware.js +36 -3
- package/dist/entries/http/requireWebAuthMiddleware.js +32 -2
- package/dist/entries/http/response.js +101 -45
- package/dist/entries/http/routeModelBinding.js +2 -2
- package/dist/entries/http/safeInternalPath.js +32 -2
- package/dist/entries/http/scimThrottleMiddleware.js +7 -1
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/http/securityHeadersMiddleware.js +3 -64
- package/dist/entries/http/signedUrl.js +6 -5
- package/dist/entries/http/throttleMiddleware.js +63 -0
- package/dist/entries/http/validation.js +2 -2
- package/dist/entries/http/webFormRequest.js +2 -2
- package/dist/entries/jobs/exportAuditLogsJob.js +223 -40
- package/dist/entries/mail/mailer.js +54 -8
- package/dist/entries/media/imageTransform.js +12 -1
- package/dist/entries/openapi/generator.js +0 -7
- package/dist/entries/security/oauthState.js +30 -39
- package/dist/entries/security/publicReads.js +1 -1
- package/dist/entries/security/recoveryCodes.js +2 -2
- package/dist/entries/security/safeFetch.js +195 -94
- package/dist/entries/security/safePath.js +25 -0
- package/dist/entries/security/scimTenantTokens.js +10 -3
- package/dist/entries/security/totp.js +16 -4
- package/dist/entries/storage/storage.js +24 -2
- package/dist/entries/tenant/databaseTenantContext.js +34 -12
- package/dist/entries/tenant/enableTenantRls.js +385 -0
- package/dist/entries/tracing/tracingMiddleware.js +308 -5
- package/dist/framework/public-api.d.ts +10 -3
- package/dist/index.js +2767 -1515
- package/package.json +32 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,53 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 1.1.0
|
|
4
|
+
|
|
5
|
+
Breaking security hardening. Claims below match the code.
|
|
6
|
+
|
|
7
|
+
### Migration
|
|
8
|
+
|
|
9
|
+
- JWT without `exp` is rejected.
|
|
10
|
+
- Non-expiring `signedUrl()` is now invalid on verify. `signedUrl()` without `expires` always fails verification.
|
|
11
|
+
- Existing API tokens hashed with unpeppered SHA256 will not match HMAC pepper hashes. Re-issue tokens after setting `TOKEN_HASH_PEPPER`.
|
|
12
|
+
- CSRF and session secret fallbacks are gone. `ADMIN_API_TOKEN` is no longer a session or CSRF secret.
|
|
13
|
+
- CORS no longer defaults to `*` outside production. Unset CORS is `APP_URL` locally and same-origin in production.
|
|
14
|
+
- CSRF cookie is HttpOnly. JavaScript cannot read it.
|
|
15
|
+
- Missing `emailVerifiedAt` now means unverified. This is a break for JWT and tokens that omitted the field.
|
|
16
|
+
- Failed Bearer no longer authenticates via the session cookie.
|
|
17
|
+
- `/ready` no longer returns checks unless `APP_DEBUG=true`.
|
|
18
|
+
- Staging `/metrics` requires a token or returns 404.
|
|
19
|
+
- Seed password is `StrataDemo!ChangeMe`.
|
|
20
|
+
- SAML `saml:email:name` stub is gone.
|
|
21
|
+
- MFA is when enrolled. Password login does not force enrollment.
|
|
22
|
+
- `signedUrl()` without `expires` always fails verification.
|
|
23
|
+
- SAML requires `SAML_IDP_ISSUER`. ACS compares assertion issuer to that value. Signed responses are required; production boot rejects `SAML_WANT_RESPONSE_SIGNED=false`.
|
|
24
|
+
- OIDC ID tokens must be RS256 with JWKS. HS256 ID tokens are rejected.
|
|
25
|
+
- Generated login tokens and JWTs mint `[]` abilities. SQL `api_tokens.abilities` default stays `[]`.
|
|
26
|
+
- `--tenancy=rls` FORCE RLS is on `notes` and `users`. Auth directory lookups and cookie session loads use `runWithMigrationBypassForIdentifier`, which sets `app.bypass_identifier`. The helper does not rewrite SQL.
|
|
27
|
+
- `GET /health`: `createHealthRoutes` without `pingOnHealth` is always 200 JSON and does not read notes. HiroApp `/health` is `schemaReady` (empty notes 200, unreadable not 200). Docker HEALTHCHECK fetches `/health`.
|
|
28
|
+
|
|
29
|
+
### Controls
|
|
30
|
+
|
|
31
|
+
- SAML ACS verifies HMAC RelayState (no SameSite cookie). Replay defaults to SQL `auth_saml_assertions.assertion_id` (unique insert). Tests may use an in-memory store. Both drop IDs after 1 hour (longer than typical assertion lifetime plus `acceptedClockSkewMs` 5000). ACS rejects a signed assertion whose issuer does not match `SAML_IDP_ISSUER`. Signed assertions and signed responses are required. AuthnContext is requested. JIT uses `currentTenantId()` and is skipped when `FEATURE_REGISTRATION=false`. `GET /auth/saml` does not set an unused OAuth state cookie. Enrolled MFA still challenges after SAML ACS.
|
|
32
|
+
- `completePasswordLogin` runs on HTML password POST, HTML MFA POST, API token, JWT, and Basic mint paths, and cookie JSON password login. Recovery-code consumption is persisted on those paths. `verifyCredentials` returns null when `mfa_enabled` is true. Basic `verifyCredentials` fallback still runs TOTP when the directory record is enrolled. MFA is when enrolled, not on every password login. JwtGuard does not run TOTP on each request; it requires a directory and rejects `iat` before `session_valid_after`. SAML ACS redirects enrolled users to `/login/mfa`. MFA enroll revokes sessions and API tokens.
|
|
33
|
+
- Password reset and email verify consume one-time tokens with `UPDATE ... consumed_at IS NULL`. Cookie sessions compare aliased `sessions.created_at` (`session_created_at`) to `session_valid_after`. Reset deletes `sessions` and `api_tokens` (missing tables only are swallowed). JwtGuard looks up the user and rejects tokens whose `iat` is before `session_valid_after`. Verify GET does not sign the visitor in.
|
|
34
|
+
- `--tenancy=rls` ENABLE+FORCE RLS is on `notes` and `users`. `sessions`, `api_tokens`, and `auth_one_time_tokens` get a user-join policy plus an `app.bypass_identifier` pin on the real key columns. Session create/destroy, token mint, password/email/MFA user writes, and one-time token insert/consume wrap `runWithMigrationBypassForIdentifier()`, which `SET LOCAL app.bypass_identifier` and does not rewrite SQL. `ForIdentifier(user id)` may see that user's sessions and tokens; that is the session-create pin, not a one-row-only policy. Consume passes `hashOneTimeToken(token)`. Notes honour unbounded `app.bypass_rls()` or tenant id only. SCIM isolation is tenant GUC plus `WHERE tenant_id`. `currentTenantId()` and generated SCIM `tenantId()` throw if ALS is missing. `createHealthRoutes` without `pingOnHealth` is always 200 JSON. HiroApp `/health` is `schemaReady` (empty notes 200, unreadable not 200). `runWithMigrationBypass()` always opens its own transaction and remains for migrate/seed/audit. Generated Compose still creates a `postgres` superuser for volume init, GRANT, migrate, and `migrate:fresh` DROP (tables are owned by that superuser). Runtime `DATABASE_URL` / HiroApp `APP_DATABASE_URL` use `strata_app` (`NOBYPASSRLS`), including `--no-docker` and host e2e via `with-host-env.sh`. `db/ensure-postgres-app-role.sql` is repeatable on an existing volume. Live `pg_roles` runs on every rls runtime pool after it is open. Username `postgres`/`root` is the URL denylist fast path. Named superuser (`deploy`) is the live inspect. The HiroApp e2e denylist test is the postgres URL fast path. The live inspect e2e is `assertRlsLiveDatabaseRole()` on the `strata_app` pool. `assertProductionSecrets()` stays production-only.
|
|
35
|
+
- SMTP rejects CR/LF. Envelope `MAIL FROM` / `RCPT TO` use the bare address. Display names stay on headers only. `MAIL_USERNAME` and `MAIL_PASSWORD` are checked before AUTH LOGIN writes. There is no SMTP e2e (`MAIL_DRIVER=log` in HiroApp e2e).
|
|
36
|
+
- SSRF blocks non-canonical IPv4 (including leading-zero / octal / hex forms), integer hosts, and mapped IPv6. DNS resolve defaults on. `safeFetch` then connects to a resolved public IP and sends the original Host plus TLS server name. `allowPrivate: true` skips DNS only outside production.
|
|
37
|
+
- API CSRF middleware runs on guest and session mutating requests. `POST /api/v1/auth/login` and `POST /api/auth/token` require double-submit CSRF. Session-mutating API (logout after cookie login) requires CSRF. `GET /api/v1/auth/csrf` Set-Cookies the HttpOnly CSRF cookie and returns that same token in JSON (it does not mint a second cookie). Nested API CSRF (`buildModuleRoutes` plus `wrap("api")`) reuses the first issued token. CSRF failures on the API group are JSON 403. Failed Bearer does not skip CSRF when `credentialSource` is null (`tests/unit/csrf.test.ts` `failed Bearer header does not skip CSRF when credentialSource is null`). Failed Bearer does not resolve a session or guest user (`tests/unit/authGuard.test.ts` `failed bearer does not fall back to a session or guest guard`). HiroApp e2e `cookie login requires CSRF and ignores garbage Bearer` is garbage Bearer still requiring CSRF on POST `/login`. Successful skip is `successful bearer or basic skips CSRF`. SCIM and SAML ACS skip CSRF by path. CORS allowlists `X-CSRF-Token` and, when reflecting a specific origin, sets `Access-Control-Allow-Credentials`. CSRF and session cookies stay `SameSite=Lax`. A foreign or missing `Origin` on a cookie mutating request is rejected even if the CSRF header matches. Cross-site SPAs stay on Bearer.
|
|
38
|
+
- OIDC `getAuthorizationUrl()` throws. Use `createAuthorization()` and pass the handshake to `exchangeCode()`. Authorization, token, and JWKS URLs come from discovery. Inbound OIDC ID tokens are verified RS256 via discovery JWKS (`iss` / `aud` / `azp` / `at_hash` / `exp` / `nbf` / `nonce`). App JWTs stay HS256 (`signJwt`). Multi-valued `aud` requires `azp` equal to the client id. `at_hash` is verified when present; omitted `access_token` plus `at_hash` throws. Missing or unverified email throws. JWKS is refetched once when the token `kid` is missing from the cache. GitHub OAuth uses `safeFetch`, always reads `/user/emails`, and throws when no verified address exists (no `{login}@users.noreply.github.com`; unverified `profile.email` is ignored).
|
|
39
|
+
- `protectMfaSecret` always encrypts and requires `KMS_ENCRYPTION_KEY`. Local and dogfood with `FEATURE_MFA=true` must set the key. When a KMS key is set, `revealMfaSecret` never returns plaintext. Production still refuses non-`enc:v1:`. Local without a key may still return plaintext. `verifyTotp` compares every window slot with `timingSafeCompareString`.
|
|
40
|
+
- Safer defaults: `FEATURE_PUBLIC_READS`, `FEATURE_SIEM_EXPORT`, and `APP_DEBUG` default off. `DEFAULT_TENANT.plan` is `free`. SQL token-ability default is `[]`. Generated token login and JWT mint insert `[]`.
|
|
41
|
+
- Token hashes are HMAC-peppered. Recovery codes are 16 bytes. bcrypt cost is 12.
|
|
42
|
+
- Identity **response** headers `x-authenticated-user-id`, `x-tenant-id`, and `x-tenant-region` are never set. CORS still allowlists `X-Authenticated-User-Id`, `X-Authenticated-User-Role`, and `X-Tenant-Id`.
|
|
43
|
+
- `GuestGuard`: production (`isProductionEnv`, including staging) is always null even when `AUTH_DEV_HEADERS=true`. Local `AUTH_DEV_HEADERS=true` still reads request headers.
|
|
44
|
+
- Postgres unique-violation `detail` is logged, not returned. Client JSON is a generic conflict message with no constraint name.
|
|
45
|
+
- Local disk paths go through `assertPathUnderRoot`.
|
|
46
|
+
- Client `x-trace-id` is ignored unless `APP_DEBUG=true` and the value is 32 hex characters.
|
|
47
|
+
- OpenAPI documents registered routes only. Generated `docs/API.md` lists the layer's live paths and does not include leftover `/webhooks` or `/billing` strings.
|
|
48
|
+
- Root Compose Redis requires `dev-redis-change-me`. Root Compose Postgres uses `dev-postgres-change-me`. Root Compose MySQL uses `dev-mysql-change-me`. Host helper `scripts/with-host-env.sh` uses those passwords and points HiroApp `APP_DATABASE_URL` at `strata_app` / `dev-strata-app-change-me`. Fixture `DATABASE_URL` stays fixture admin for `bun_testing_test`. Production Compose Redis requires `REDIS_PASSWORD` and requires `APP_ENV` to be set. Production Compose `app`/`worker` runtime `DATABASE_URL` and `APP_DATABASE_URL` are `strata_app` after the split. `MIGRATION_DATABASE_URL` stays `${POSTGRES_USER}` for migrate. `STRATA_APP_PASSWORD` is required the same way `REDIS_PASSWORD` is. Bind stays `127.0.0.1:3000`. `127.0.0.1:54329` / `6379` / `33061` stay published. Adminer is debug-profile only. Prod compose file test plus HiroApp live-role e2e. This CI does not compose-up `docker-compose.prod.yml`.
|
|
49
|
+
|
|
50
|
+
|
|
3
51
|
## 1.0.9
|
|
4
52
|
|
|
5
53
|
Label HiroApp as internal e2e dogfood and seed notes via Model
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
type CredentialSource = "session" | "bearer" | "basic" | "guest" | null;
|
|
1
2
|
type AuthUser = {
|
|
2
3
|
id: number | string;
|
|
3
4
|
role?: string;
|
|
@@ -7,6 +8,8 @@ type AuthUser = {
|
|
|
7
8
|
};
|
|
8
9
|
declare const authContext: import("node:async_hooks").AsyncLocalStorage<AuthUser | null>;
|
|
9
10
|
declare function runWithAuthUser<T>(user: AuthUser | null, callback: () => T | Promise<T>): T | Promise<T>;
|
|
11
|
+
declare function runWithAuthContext<T>(user: AuthUser | null, credentialSource: CredentialSource, callback: () => T | Promise<T>): T | Promise<T>;
|
|
10
12
|
declare function currentAuthUser(): AuthUser | null;
|
|
11
|
-
|
|
12
|
-
export {
|
|
13
|
+
declare function currentCredentialSource(): CredentialSource;
|
|
14
|
+
export type { AuthUser, CredentialSource };
|
|
15
|
+
export { authContext, currentAuthUser, currentCredentialSource, runWithAuthContext, runWithAuthUser, };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AuthUser } from "./authContext";
|
|
2
2
|
declare function isEmailVerificationRequired(): boolean;
|
|
3
|
-
/**
|
|
3
|
+
/** Missing, undefined, or null means unverified. */
|
|
4
4
|
declare function hasVerifiedEmail(user: AuthUser): boolean;
|
|
5
5
|
export { hasVerifiedEmail, isEmailVerificationRequired };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ServiceContainerLike } from "../contracts/serviceContainer";
|
|
2
|
-
import type { AuthUser } from "./authContext";
|
|
2
|
+
import type { AuthUser, CredentialSource } from "./authContext";
|
|
3
3
|
interface AuthGuard {
|
|
4
4
|
resolve(request: Request): AuthUser | null | Promise<AuthUser | null>;
|
|
5
5
|
}
|
|
@@ -32,6 +32,10 @@ declare class AuthManager {
|
|
|
32
32
|
use(name?: string): AuthGuard;
|
|
33
33
|
guardNames(): string[];
|
|
34
34
|
resolve(request?: Request): Promise<AuthUser | null>;
|
|
35
|
+
resolveWithSource(request?: Request): Promise<{
|
|
36
|
+
user: AuthUser | null;
|
|
37
|
+
credentialSource: CredentialSource;
|
|
38
|
+
}>;
|
|
35
39
|
user(request?: Request): Promise<AuthUser | null>;
|
|
36
40
|
check(request?: Request): Promise<boolean>;
|
|
37
41
|
requireUser(request?: Request): Promise<AuthUser>;
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import type { AuthUserDirectory } from "../contracts/authUserDirectory";
|
|
2
|
+
import type { ServiceContainerLike } from "../contracts/serviceContainer";
|
|
1
3
|
import type { AuthUser } from "./authContext";
|
|
2
4
|
import type { AuthGuard } from "./guard";
|
|
3
5
|
import { type JwtPayload } from "./jwt";
|
|
4
6
|
interface JwtGuardOptions {
|
|
5
7
|
secret?: string;
|
|
8
|
+
directory?: AuthUserDirectory;
|
|
9
|
+
container?: ServiceContainerLike;
|
|
6
10
|
}
|
|
7
11
|
declare function authUserFromJwt(payload: JwtPayload): AuthUser;
|
|
8
12
|
declare class JwtGuard implements AuthGuard {
|
|
9
13
|
private readonly options;
|
|
10
|
-
constructor(options?: JwtGuardOptions);
|
|
11
|
-
resolve(request: Request): AuthUser | null
|
|
14
|
+
constructor(options?: JwtGuardOptions | ServiceContainerLike);
|
|
15
|
+
resolve(request: Request): Promise<AuthUser | null>;
|
|
12
16
|
}
|
|
13
17
|
export type { JwtGuardOptions };
|
|
14
18
|
export { authUserFromJwt, JwtGuard };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { JwtPayload } from "../jwt";
|
|
2
|
+
interface OidcDiscovery {
|
|
3
|
+
issuer?: string;
|
|
4
|
+
authorization_endpoint?: string;
|
|
5
|
+
token_endpoint?: string;
|
|
6
|
+
jwks_uri?: string;
|
|
7
|
+
}
|
|
8
|
+
declare function loadOidcDiscovery(issuer: string): Promise<OidcDiscovery>;
|
|
9
|
+
declare function oidcAccessTokenHash(accessToken: string): string;
|
|
10
|
+
declare function verifyOidcIdToken(token: string, options: {
|
|
11
|
+
issuer: string;
|
|
12
|
+
clientId: string;
|
|
13
|
+
nonce: string;
|
|
14
|
+
jwksUri: string;
|
|
15
|
+
accessToken?: string;
|
|
16
|
+
}): Promise<JwtPayload>;
|
|
17
|
+
declare function resetOidcDiscoveryCacheForTests(): void;
|
|
18
|
+
export type { OidcDiscovery };
|
|
19
|
+
export { loadOidcDiscovery, oidcAccessTokenHash, resetOidcDiscoveryCacheForTests, verifyOidcIdToken, };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resetOidcDiscoveryCacheForTests } from "./oidcIdToken";
|
|
1
2
|
import type { OAuthProfile, OAuthProvider } from "./types";
|
|
2
3
|
interface OidcProviderOptions {
|
|
3
4
|
name: string;
|
|
@@ -7,12 +8,23 @@ interface OidcProviderOptions {
|
|
|
7
8
|
redirectUri: string;
|
|
8
9
|
scopes?: string[];
|
|
9
10
|
}
|
|
11
|
+
interface OidcHandshake {
|
|
12
|
+
state: string;
|
|
13
|
+
nonce: string;
|
|
14
|
+
codeVerifier: string;
|
|
15
|
+
}
|
|
16
|
+
declare function createOidcHandshake(): OidcHandshake;
|
|
10
17
|
declare class OidcProvider implements OAuthProvider {
|
|
11
18
|
private readonly options;
|
|
12
19
|
readonly name: string;
|
|
13
20
|
constructor(options: OidcProviderOptions);
|
|
14
|
-
getAuthorizationUrl(
|
|
15
|
-
|
|
21
|
+
getAuthorizationUrl(_state: string, _redirectUri?: string): string;
|
|
22
|
+
createAuthorization(state?: string, redirectUri?: string): Promise<{
|
|
23
|
+
url: string;
|
|
24
|
+
handshake: OidcHandshake;
|
|
25
|
+
}>;
|
|
26
|
+
private buildAuthorizationUrl;
|
|
27
|
+
exchangeCode(code: string, redirectUri?: string, handshake?: Pick<OidcHandshake, "nonce" | "codeVerifier">): Promise<OAuthProfile>;
|
|
16
28
|
}
|
|
17
|
-
export type { OidcProviderOptions };
|
|
18
|
-
export { OidcProvider };
|
|
29
|
+
export type { OidcHandshake, OidcProviderOptions };
|
|
30
|
+
export { createOidcHandshake, OidcProvider, resetOidcDiscoveryCacheForTests };
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
import { type SamlServiceProviderOptions } from "../saml/samlServiceProvider";
|
|
1
2
|
import type { OAuthProfile, OAuthProvider } from "./types";
|
|
2
3
|
declare class SamlProvider implements OAuthProvider {
|
|
3
|
-
private readonly loginUrl;
|
|
4
4
|
readonly name = "saml";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
private readonly provider;
|
|
6
|
+
constructor(options: SamlServiceProviderOptions | string);
|
|
7
|
+
getAuthorizationUrl(_state: string): string;
|
|
8
|
+
exchangeCode(_code: string): Promise<OAuthProfile>;
|
|
9
|
+
authorizationUrl(relayState: string): Promise<string>;
|
|
10
|
+
consumePost(samlResponse: string, relayState?: string): Promise<OAuthProfile>;
|
|
8
11
|
}
|
|
9
12
|
export { SamlProvider };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const AUTH_ONE_TIME_PURPOSES: {
|
|
2
|
+
readonly passwordReset: "password_reset";
|
|
3
|
+
readonly emailVerify: "email_verify";
|
|
4
|
+
};
|
|
5
|
+
type AuthOneTimePurpose = (typeof AUTH_ONE_TIME_PURPOSES)[keyof typeof AUTH_ONE_TIME_PURPOSES];
|
|
6
|
+
declare function generateOneTimeToken(): {
|
|
7
|
+
plain: string;
|
|
8
|
+
hash: string;
|
|
9
|
+
};
|
|
10
|
+
declare function hashOneTimeToken(plain: string): string;
|
|
11
|
+
type SqlClient = {
|
|
12
|
+
unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
|
|
13
|
+
};
|
|
14
|
+
declare function consumeOneTimeToken(sql: SqlClient, purpose: string, plain: string, now?: Date): Promise<number | null>;
|
|
15
|
+
declare function revokeUserSessions(sql: SqlClient, userId: number, options?: {
|
|
16
|
+
revokeApiTokens?: boolean;
|
|
17
|
+
}): Promise<void>;
|
|
18
|
+
export type { AuthOneTimePurpose };
|
|
19
|
+
export { AUTH_ONE_TIME_PURPOSES, consumeOneTimeToken, generateOneTimeToken, hashOneTimeToken, revokeUserSessions, };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AuthUserRecord } from "../contracts/authUserDirectory";
|
|
2
|
+
type PasswordLoginFailure = "mfa_required" | "mfa_invalid";
|
|
3
|
+
type PasswordLoginSuccess = {
|
|
4
|
+
ok: true;
|
|
5
|
+
consumedRecoveryHash?: string;
|
|
6
|
+
};
|
|
7
|
+
type PasswordLoginDenied = {
|
|
8
|
+
ok: false;
|
|
9
|
+
error: PasswordLoginFailure;
|
|
10
|
+
};
|
|
11
|
+
type PasswordLoginResult = PasswordLoginSuccess | PasswordLoginDenied;
|
|
12
|
+
declare function parseRecoveryHashes(raw: string | null | undefined): string[];
|
|
13
|
+
declare function completePasswordLogin(user: Pick<AuthUserRecord, "mfa_enabled" | "mfa_secret" | "mfa_recovery_codes">, options?: {
|
|
14
|
+
mfaCode?: string | null;
|
|
15
|
+
}): PasswordLoginResult;
|
|
16
|
+
declare function persistConsumedRecoveryHash(sql: {
|
|
17
|
+
unsafe(query: string, params?: readonly unknown[]): Promise<unknown[]>;
|
|
18
|
+
}, userId: number, currentRaw: string | null | undefined, consumedHash: string | undefined): Promise<void>;
|
|
19
|
+
export type { PasswordLoginResult };
|
|
20
|
+
export { completePasswordLogin, parseRecoveryHashes, persistConsumedRecoveryHash };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
interface SamlAssertionReplayStore {
|
|
2
|
+
consume(assertionId: string): Promise<void>;
|
|
3
|
+
}
|
|
4
|
+
/**
|
|
5
|
+
* Drop stored IDs only after SAML NotOnOrAfter would already reject them.
|
|
6
|
+
* 1 hour is longer than typical assertion lifetimes (minutes) plus
|
|
7
|
+
* SamlServiceProvider acceptedClockSkewMs (5000).
|
|
8
|
+
*/
|
|
9
|
+
declare const REPLAY_RETENTION_MS: number;
|
|
10
|
+
declare class InMemorySamlAssertionReplayStore implements SamlAssertionReplayStore {
|
|
11
|
+
private readonly seen;
|
|
12
|
+
consume(assertionId: string): Promise<void>;
|
|
13
|
+
clear(): void;
|
|
14
|
+
private gc;
|
|
15
|
+
}
|
|
16
|
+
declare function setSamlAssertionReplayStoreForTests(store: SamlAssertionReplayStore | null): void;
|
|
17
|
+
declare function resetSamlReplayCacheForTests(): void;
|
|
18
|
+
declare function consumeSamlAssertion(assertionId: string): Promise<void>;
|
|
19
|
+
export type { SamlAssertionReplayStore };
|
|
20
|
+
export { consumeSamlAssertion, InMemorySamlAssertionReplayStore, REPLAY_RETENTION_MS, resetSamlReplayCacheForTests, setSamlAssertionReplayStoreForTests, };
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { OAuthProfile } from "../oauth/types";
|
|
2
|
+
import { consumeSamlAssertion, InMemorySamlAssertionReplayStore, REPLAY_RETENTION_MS, resetSamlReplayCacheForTests, setSamlAssertionReplayStoreForTests } from "./samlAssertionReplay";
|
|
3
|
+
interface SamlServiceProviderOptions {
|
|
4
|
+
idpSsoUrl: string;
|
|
5
|
+
idpCert: string;
|
|
6
|
+
spEntityId: string;
|
|
7
|
+
acsUrl: string;
|
|
8
|
+
idpIssuer: string;
|
|
9
|
+
wantAssertionsSigned?: boolean;
|
|
10
|
+
wantAuthnResponseSigned?: boolean;
|
|
11
|
+
disableRequestedAuthnContext?: boolean;
|
|
12
|
+
}
|
|
13
|
+
interface NodeSamlProfile {
|
|
14
|
+
ID?: string;
|
|
15
|
+
nameID?: string;
|
|
16
|
+
email?: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
issuer?: string;
|
|
19
|
+
sessionIndex?: string | string[];
|
|
20
|
+
getAssertion?: () => {
|
|
21
|
+
Assertion?: {
|
|
22
|
+
$?: {
|
|
23
|
+
ID?: string;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
type NodeSamlClient = {
|
|
30
|
+
getAuthorizeUrlAsync(relayState: string, host: string | undefined, options: object): Promise<string>;
|
|
31
|
+
validatePostResponseAsync(container: Record<string, string>): Promise<{
|
|
32
|
+
profile: NodeSamlProfile | null;
|
|
33
|
+
loggedOut: boolean;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
36
|
+
declare function readSamlEnvOptions(): SamlServiceProviderOptions;
|
|
37
|
+
type NodeSamlModule = {
|
|
38
|
+
SAML: new (options: Record<string, unknown>) => NodeSamlClient;
|
|
39
|
+
};
|
|
40
|
+
declare function setNodeSamlLoaderForTests(loader: (() => Promise<NodeSamlModule>) | null): void;
|
|
41
|
+
declare class SamlServiceProvider {
|
|
42
|
+
private readonly options;
|
|
43
|
+
private client;
|
|
44
|
+
constructor(options: SamlServiceProviderOptions);
|
|
45
|
+
private getClient;
|
|
46
|
+
authorizationUrl(relayState: string): Promise<string>;
|
|
47
|
+
consumePost(samlResponse: string, relayState?: string): Promise<OAuthProfile>;
|
|
48
|
+
}
|
|
49
|
+
declare function createSamlServiceProvider(options?: SamlServiceProviderOptions): SamlServiceProvider;
|
|
50
|
+
export type { SamlServiceProviderOptions };
|
|
51
|
+
export { consumeSamlAssertion, createSamlServiceProvider, InMemorySamlAssertionReplayStore, REPLAY_RETENTION_MS, readSamlEnvOptions, resetSamlReplayCacheForTests, SamlServiceProvider, setNodeSamlLoaderForTests, setSamlAssertionReplayStoreForTests, };
|
|
@@ -6,6 +6,7 @@ interface AuthUserRecord {
|
|
|
6
6
|
role: string;
|
|
7
7
|
email_verified_at?: Date | string | null;
|
|
8
8
|
session_valid_after?: Date | string | null;
|
|
9
|
+
password?: string | null;
|
|
9
10
|
mfa_enabled?: boolean;
|
|
10
11
|
mfa_secret?: string | null;
|
|
11
12
|
mfa_recovery_codes?: string | null;
|
|
@@ -14,9 +15,7 @@ interface AuthUserDirectory {
|
|
|
14
15
|
resolveUserFromToken(token: string): Promise<AuthUser | null>;
|
|
15
16
|
findByIdOrThrow(id: number): Promise<AuthUserRecord>;
|
|
16
17
|
hasActiveBrowserSession?(userId: number, issuedAt: number): Promise<boolean>;
|
|
17
|
-
findByEmail?(email: string): Promise<
|
|
18
|
-
password?: string | null;
|
|
19
|
-
}) | null>;
|
|
18
|
+
findByEmail?(email: string): Promise<AuthUserRecord | null>;
|
|
20
19
|
verifyCredentials?(email: string, password: string): Promise<AuthUser | null>;
|
|
21
20
|
}
|
|
22
21
|
export type { AuthUserDirectory, AuthUserRecord };
|
|
@@ -13,4 +13,5 @@ declare function isPostgresError(error: unknown): error is PostgresErrorLike;
|
|
|
13
13
|
*/
|
|
14
14
|
declare function mapDatabaseError(error: unknown): HttpError;
|
|
15
15
|
declare function withDatabaseErrorHandling<TValue>(operation: () => Promise<TValue>): Promise<TValue>;
|
|
16
|
-
|
|
16
|
+
declare function isUniqueConstraintError(error: unknown): boolean;
|
|
17
|
+
export { isPostgresError, isUniqueConstraintError, mapDatabaseError, withDatabaseErrorHandling };
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { Middleware } from "./middleware";
|
|
1
2
|
declare function jsonResponse(data: unknown, init?: ResponseInit): Response;
|
|
2
3
|
declare function createdResponse(data: unknown, init?: ResponseInit): Response;
|
|
3
4
|
declare function noContentResponse(): Response;
|
|
4
5
|
declare function errorResponse(error: unknown): Response;
|
|
5
6
|
declare function withErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
|
|
6
7
|
declare function withJsonErrorHandling<TArgs extends unknown[]>(handler: (...args: TArgs) => Response | Promise<Response>): (...args: TArgs) => Promise<Response>;
|
|
7
|
-
|
|
8
|
+
declare function createJsonErrorMiddleware(): Middleware;
|
|
9
|
+
export { createdResponse, createJsonErrorMiddleware, errorResponse, jsonResponse, noContentResponse, withErrorHandling, withJsonErrorHandling, };
|
|
@@ -17,6 +17,11 @@ interface SmtpConfig {
|
|
|
17
17
|
}
|
|
18
18
|
type SmtpTransport = (config: SmtpConfig, message: MailMessage) => Promise<void>;
|
|
19
19
|
declare function resolveSmtpConfig(): SmtpConfig;
|
|
20
|
+
declare function assertSafeSmtpField(value: string, field: string): string;
|
|
21
|
+
declare function smtpEnvelopeAddress(value: string, field: string): string;
|
|
22
|
+
declare function assertSafeSmtpAddress(value: string, field: string): string;
|
|
23
|
+
declare function redactMailBody(body: string): string;
|
|
24
|
+
declare function defaultSmtpTransport(config: SmtpConfig, message: MailMessage): Promise<void>;
|
|
20
25
|
declare function buildSmtpPayload(from: string, message: MailMessage): string;
|
|
21
26
|
declare class LogMailDriver implements MailDriver {
|
|
22
27
|
send(message: MailMessage): Promise<void>;
|
|
@@ -35,4 +40,4 @@ declare class Mailer {
|
|
|
35
40
|
declare function createMailDriver(): MailDriver;
|
|
36
41
|
declare function mailer(): Mailer;
|
|
37
42
|
export type { MailDriver, MailMessage, SmtpConfig, SmtpTransport };
|
|
38
|
-
export { buildSmtpPayload, createMailDriver, LogMailDriver, Mailer, mailer, resolveSmtpConfig, SmtpMailDriver, };
|
|
43
|
+
export { assertSafeSmtpAddress, assertSafeSmtpField, buildSmtpPayload, createMailDriver, defaultSmtpTransport, LogMailDriver, Mailer, mailer, redactMailBody, resolveSmtpConfig, SmtpMailDriver, smtpEnvelopeAddress, };
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
declare const OAUTH_STATE_COOKIE = "oauth_state";
|
|
2
|
+
declare function verifySignedOAuthStateValue(value: string): boolean;
|
|
3
|
+
declare function createOAuthState(): {
|
|
4
|
+
state: string;
|
|
5
|
+
};
|
|
2
6
|
declare function createOAuthStateCookie(): {
|
|
3
7
|
state: string;
|
|
4
8
|
cookie: string;
|
|
5
9
|
};
|
|
6
|
-
declare function verifyOAuthState(
|
|
10
|
+
declare function verifyOAuthState(_request: Request, returnedState: string | null): boolean;
|
|
7
11
|
declare function clearOAuthStateCookie(): string;
|
|
8
|
-
export { clearOAuthStateCookie, createOAuthStateCookie, OAUTH_STATE_COOKIE, verifyOAuthState };
|
|
12
|
+
export { clearOAuthStateCookie, createOAuthState, createOAuthStateCookie, OAUTH_STATE_COOKIE, verifyOAuthState, verifySignedOAuthStateValue, };
|
|
@@ -4,6 +4,7 @@ interface SafeFetchOptions {
|
|
|
4
4
|
maxRedirects?: number;
|
|
5
5
|
allowHttp?: boolean;
|
|
6
6
|
resolveDns?: boolean;
|
|
7
|
+
allowPrivate?: boolean;
|
|
7
8
|
}
|
|
8
9
|
declare function safeFetch(input: string, init?: RequestInit, options?: SafeFetchOptions): Promise<Response>;
|
|
9
10
|
export type { SafeFetchOptions };
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
declare function assertPathUnderRoot(rootDirectory: string, userPath: string): string;
|
|
2
|
+
/** Map a URL pathname onto a document root. Leading slashes are not filesystem-absolute. */
|
|
3
|
+
declare function assertUrlPathUnderRoot(rootDirectory: string, urlPathname: string): string;
|
|
4
|
+
export { assertPathUnderRoot, assertUrlPathUnderRoot };
|
|
@@ -9,12 +9,25 @@ type DnsLookup = (hostname: string, options: {
|
|
|
9
9
|
declare function isBlockedHostname(hostname: string): boolean;
|
|
10
10
|
declare function assertSafeOutboundUrl(rawUrl: string, options?: {
|
|
11
11
|
allowHttp?: boolean;
|
|
12
|
+
allowPrivate?: boolean;
|
|
12
13
|
}): URL;
|
|
13
14
|
declare function isBlockedIpAddress(address: string): boolean;
|
|
15
|
+
declare function pinUrlToAddress(url: URL, address: string): URL;
|
|
16
|
+
interface ResolvedOutboundUrl {
|
|
17
|
+
url: URL;
|
|
18
|
+
addresses: string[];
|
|
19
|
+
}
|
|
20
|
+
declare function resolveSafeOutboundTarget(rawUrl: string, options?: {
|
|
21
|
+
allowHttp?: boolean;
|
|
22
|
+
resolveDns?: boolean;
|
|
23
|
+
allowPrivate?: boolean;
|
|
24
|
+
}): Promise<ResolvedOutboundUrl>;
|
|
14
25
|
declare function assertSafeOutboundUrlResolved(rawUrl: string, options?: {
|
|
15
26
|
allowHttp?: boolean;
|
|
16
27
|
resolveDns?: boolean;
|
|
28
|
+
allowPrivate?: boolean;
|
|
17
29
|
}): Promise<URL>;
|
|
18
30
|
declare function setDnsLookupForTests(lookupFn: DnsLookup): void;
|
|
19
31
|
declare function resetDnsLookupForTests(): void;
|
|
20
|
-
export {
|
|
32
|
+
export type { ResolvedOutboundUrl };
|
|
33
|
+
export { assertSafeOutboundUrl, assertSafeOutboundUrlResolved, isBlockedHostname, isBlockedIpAddress, pinUrlToAddress, resetDnsLookupForTests, resolveSafeOutboundTarget, setDnsLookupForTests, };
|
|
@@ -4,6 +4,6 @@ declare function buildOtpauthUrl(options: {
|
|
|
4
4
|
account: string;
|
|
5
5
|
issuer?: string;
|
|
6
6
|
}): string;
|
|
7
|
-
declare function generateTotp(secret: string, counter
|
|
7
|
+
declare function generateTotp(secret: string, counter?: number, digits?: number): string;
|
|
8
8
|
declare function verifyTotp(secret: string, token: string, window?: number): boolean;
|
|
9
9
|
export { buildOtpauthUrl, generateTotp, generateTotpSecret, verifyTotp };
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
declare function runWithMigrationBypass<T>(callback: () => T | Promise<T>): Promise<T>;
|
|
2
|
-
|
|
2
|
+
declare function runWithMigrationBypassForIdentifier<T>(identifier: string | number, callback: () => T | Promise<T>): Promise<T>;
|
|
3
|
+
export { runWithMigrationBypass, runWithMigrationBypassForIdentifier };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
declare const RLS_HELPER_SQL = "\nCREATE OR REPLACE FUNCTION app_bypass_rls()\nRETURNS BOOLEAN AS $$\nBEGIN\n RETURN COALESCE(current_setting('app.bypass_rls', true), 'false') = 'true';\nEXCEPTION\n WHEN others THEN\n RETURN FALSE;\nEND;\n$$ LANGUAGE plpgsql STABLE;\n\nCREATE OR REPLACE FUNCTION app_bypass_identifier()\nRETURNS TEXT AS $$\nBEGIN\n RETURN NULLIF(current_setting('app.bypass_identifier', true), '');\nEXCEPTION\n WHEN others THEN\n RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql STABLE;\n\nCREATE OR REPLACE FUNCTION app_current_tenant_id()\nRETURNS INTEGER AS $$\nBEGIN\n RETURN NULLIF(current_setting('app.tenant_id', true), '')::INTEGER;\nEXCEPTION\n WHEN others THEN\n RETURN NULL;\nEND;\n$$ LANGUAGE plpgsql STABLE;\n";
|
|
2
|
+
declare function enableTenantRlsSql(tableName: string): string;
|
|
3
|
+
declare function enableUserOwnedRlsSql(tableName: string, userIdColumn?: string): string;
|
|
4
|
+
declare function generatedRlsBootstrapSql(tables: readonly string[], userOwnedTables?: readonly string[]): string;
|
|
5
|
+
export { assertPostgresRoleCannotBypassRls, dropPostgresTablesAsAdmin, ensurePostgresAppRole, ensurePostgresDatabaseAndAppRole, grantPostgresAppRolePrivileges, inspectCurrentPostgresRole, isPostgresUrl, openPostgresAdminConnection, POSTGRES_APP_ROLE, POSTGRES_APP_ROLE_PASSWORD, POSTGRES_SUPERUSER_PASSWORD, postgresAppRoleSql, postgresDatabaseNameFromUrl, postgresUrlUsername, } from "./postgresAppRole";
|
|
6
|
+
export { enableTenantRlsSql, enableUserOwnedRlsSql, generatedRlsBootstrapSql, RLS_HELPER_SQL };
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Postgres login role for FORCE RLS.
|
|
3
|
+
*
|
|
4
|
+
* Superuser (typically `postgres`) may CREATE ROLE, CREATE DATABASE, GRANT,
|
|
5
|
+
* migrate, and migrate:fresh DROP TABLE. Tables are owned by the role that ran
|
|
6
|
+
* CREATE TABLE, so the NOBYPASSRLS runtime role cannot drop them. Runtime
|
|
7
|
+
* DATABASE_URL / APP_DATABASE_URL must be this NOBYPASSRLS role.
|
|
8
|
+
* docker-entrypoint-initdb.d only runs on an empty volume; call
|
|
9
|
+
* ensurePostgresDatabaseAndAppRole() on every boot so existing volumes get the
|
|
10
|
+
* same role and table GRANTs after migrate:fresh.
|
|
11
|
+
*
|
|
12
|
+
* Admin DDL (GRANT ALL TABLES, CREATE TABLE, DROP TABLE) must run on the
|
|
13
|
+
* application database. postgres/template1 are only for CREATE DATABASE.
|
|
14
|
+
*/
|
|
15
|
+
declare const POSTGRES_APP_ROLE = "strata_app";
|
|
16
|
+
declare const POSTGRES_APP_ROLE_PASSWORD = "dev-strata-app-change-me";
|
|
17
|
+
declare const POSTGRES_SUPERUSER_PASSWORD = "dev-postgres-change-me";
|
|
18
|
+
declare const LEGACY_POSTGRES_SUPERUSER_PASSWORD = "postgres";
|
|
19
|
+
type PostgresSql = {
|
|
20
|
+
unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
|
|
21
|
+
close?: () => Promise<void> | void;
|
|
22
|
+
};
|
|
23
|
+
type ConnectPostgres = (url: string) => PostgresSql;
|
|
24
|
+
type LivePostgresRole = {
|
|
25
|
+
rolname: string;
|
|
26
|
+
rolsuper: boolean;
|
|
27
|
+
rolbypassrls: boolean;
|
|
28
|
+
};
|
|
29
|
+
type PostgresAdminOptions = {
|
|
30
|
+
runtimeUrl: string;
|
|
31
|
+
migrationUrl?: string;
|
|
32
|
+
superuserPassword?: string;
|
|
33
|
+
connect?: ConnectPostgres;
|
|
34
|
+
};
|
|
35
|
+
declare function assertSafeSqlIdentifier(identifier: string, label: string): string;
|
|
36
|
+
declare function quoteSqlLiteral(value: string): string;
|
|
37
|
+
declare function isPostgresUrl(raw: string | undefined): boolean;
|
|
38
|
+
declare function postgresUrlUsername(raw: string): string | null;
|
|
39
|
+
declare function postgresDatabaseNameFromUrl(raw: string): string;
|
|
40
|
+
declare function postgresAdminUrls(options: {
|
|
41
|
+
runtimeUrl: string;
|
|
42
|
+
migrationUrl?: string;
|
|
43
|
+
superuserPassword?: string;
|
|
44
|
+
}): string[];
|
|
45
|
+
declare function openPostgresAdminConnection(options: PostgresAdminOptions): Promise<PostgresSql>;
|
|
46
|
+
declare function postgresAppRoleCreateSql(options?: {
|
|
47
|
+
role?: string;
|
|
48
|
+
password?: string;
|
|
49
|
+
}): string;
|
|
50
|
+
declare function postgresAppRoleSql(options: {
|
|
51
|
+
database: string;
|
|
52
|
+
role?: string;
|
|
53
|
+
password?: string;
|
|
54
|
+
}): string;
|
|
55
|
+
declare function grantPostgresAppRolePrivileges(sql: PostgresSql, options: {
|
|
56
|
+
database: string;
|
|
57
|
+
role?: string;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
declare function ensurePostgresAppRole(sql: PostgresSql, options: {
|
|
60
|
+
database: string;
|
|
61
|
+
role?: string;
|
|
62
|
+
password?: string;
|
|
63
|
+
}): Promise<void>;
|
|
64
|
+
declare function ensurePostgresDatabaseAndAppRole(options: {
|
|
65
|
+
runtimeUrl: string;
|
|
66
|
+
migrationUrl?: string;
|
|
67
|
+
role?: string;
|
|
68
|
+
password?: string;
|
|
69
|
+
connect?: ConnectPostgres;
|
|
70
|
+
}): Promise<string>;
|
|
71
|
+
declare function dropPostgresTablesAsAdmin(tables: readonly string[], options: PostgresAdminOptions): Promise<void>;
|
|
72
|
+
declare function inspectCurrentPostgresRole(sql: PostgresSql): Promise<LivePostgresRole | null>;
|
|
73
|
+
declare function assertPostgresRoleCannotBypassRls(role: LivePostgresRole | null, source?: string): void;
|
|
74
|
+
export type { ConnectPostgres, LivePostgresRole, PostgresSql };
|
|
75
|
+
export { assertPostgresRoleCannotBypassRls, assertSafeSqlIdentifier, dropPostgresTablesAsAdmin, ensurePostgresAppRole, ensurePostgresDatabaseAndAppRole, grantPostgresAppRolePrivileges, inspectCurrentPostgresRole, isPostgresUrl, LEGACY_POSTGRES_SUPERUSER_PASSWORD, openPostgresAdminConnection, POSTGRES_APP_ROLE, POSTGRES_APP_ROLE_PASSWORD, POSTGRES_SUPERUSER_PASSWORD, postgresAdminUrls, postgresAppRoleCreateSql, postgresAppRoleSql, postgresDatabaseNameFromUrl, postgresUrlUsername, quoteSqlLiteral, };
|