diff --git a/.env.example b/.env.example index 1ea3e70..c286132 100644 --- a/.env.example +++ b/.env.example @@ -21,6 +21,9 @@ DATABASE_URL=sqlite://data/gitea-mirror.db BETTER_AUTH_SECRET=change-this-to-a-secure-random-string-in-production BETTER_AUTH_URL=http://localhost:4321 # ENCRYPTION_SECRET=optional-encryption-key-for-token-encryption # Generate with: openssl rand -base64 48 +# Better Auth log verbosity: debug | info | warn | error (default: warn). +# Set to "debug" to trace SSO/OIDC sign-in and callback issues. +# BETTER_AUTH_LOG_LEVEL=debug # =========================================== # REVERSE PROXY CONFIGURATION diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 9cab4e1..830d23d 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -88,6 +88,24 @@ jobs: - name: Start Gitea and git-server containers run: | + # Retry pulls before `up` — Docker Hub occasionally returns + # "context deadline exceeded" on the first attempt. Pulling + # separately also keeps the failure mode obvious if it persists. + echo "Pulling images..." + for attempt in 1 2 3; do + if docker compose -f tests/e2e/docker-compose.e2e.yml pull --quiet; then + echo "✓ Images pulled (attempt $attempt)" + break + fi + if [ "$attempt" -eq 3 ]; then + echo "ERROR: docker compose pull failed after 3 attempts" + exit 1 + fi + sleep_s=$((attempt * 10)) + echo "Pull attempt $attempt failed, retrying in ${sleep_s}s..." + sleep "$sleep_s" + done + echo "Starting containers via docker compose..." docker compose -f tests/e2e/docker-compose.e2e.yml up -d diff --git a/.gitignore b/.gitignore index 9cbd746..2c66fb0 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ tests/e2e/git-repos/ /blob-report/ /playwright/.cache/ /playwright/.auth/ +.playwright-mcp/ diff --git a/bun.lock b/bun.lock index 89d5a87..cbcaa24 100644 --- a/bun.lock +++ b/bun.lock @@ -9,6 +9,7 @@ "@astrojs/mdx": "5.0.0", "@astrojs/node": "10.0.1", "@astrojs/react": "^5.0.0", + "@better-auth/oauth-provider": "1.6.11", "@better-auth/sso": "1.6.11", "@octokit/plugin-throttling": "^11.0.3", "@octokit/rest": "^22.0.1", @@ -175,6 +176,8 @@ "@better-auth/mongo-adapter": ["@better-auth/mongo-adapter@1.6.11", "", { "peerDependencies": { "@better-auth/core": "^1.6.11", "@better-auth/utils": "0.4.0", "mongodb": "^6.0.0 || ^7.0.0" }, "optionalPeers": ["mongodb"] }, "sha512-3Tor8rSv8vSEIMEaV2PFpPEuVhqc1gNoZ6eGvoh3LwExXXuj8madew6ob+H1pH7Aphn3Ar5PQ08AguT8TbwFAA=="], + "@better-auth/oauth-provider": ["@better-auth/oauth-provider@1.6.11", "", { "dependencies": { "jose": "^6.1.3", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.11", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "better-auth": "^1.6.11", "better-call": "1.3.5" } }, "sha512-iMywpOEAiAUdtvpaRS8yKye+wO3AieOB3Sfv8czkmPduzFuKBICCWuOEAElQEk5tQz3vzWx64zNlLBkgEAOhuw=="], + "@better-auth/prisma-adapter": ["@better-auth/prisma-adapter@1.6.11", "", { "peerDependencies": { "@better-auth/core": "^1.6.11", "@better-auth/utils": "0.4.0", "@prisma/client": "^5.0.0 || ^6.0.0 || ^7.0.0", "prisma": "^5.0.0 || ^6.0.0 || ^7.0.0" }, "optionalPeers": ["@prisma/client", "prisma"] }, "sha512-Pw+7q7zTp+VSci1V+CYMvuxIbAeVMZLe4lRo46LJoAKMHfjFl5T/ycsyFvWs/DkWC7n9gZZzRDEbHp0I5FiKKw=="], "@better-auth/sso": ["@better-auth/sso@1.6.11", "", { "dependencies": { "fast-xml-parser": "^5.5.7", "jose": "^6.1.3", "samlify": "~2.10.2", "tldts": "^6.1.0", "zod": "^4.3.6" }, "peerDependencies": { "@better-auth/core": "^1.6.11", "@better-auth/utils": "0.4.0", "@better-fetch/fetch": "1.1.21", "better-auth": "^1.6.11", "better-call": "1.3.5" } }, "sha512-lJHmoCayp9Woh/MPKTHDfGq7k1oQbU2yz5tIOZXl/pzrgLxV7fMGo9aJCyabHkw3GHMjBes4byC6aakHYzpZIg=="], diff --git a/docs/ENVIRONMENT_VARIABLES.md b/docs/ENVIRONMENT_VARIABLES.md index 0fc2541..29e0767 100644 --- a/docs/ENVIRONMENT_VARIABLES.md +++ b/docs/ENVIRONMENT_VARIABLES.md @@ -40,6 +40,7 @@ Essential application settings required for running Gitea Mirror. | `BETTER_AUTH_URL` | Authentication origin (scheme + host only, e.g. `https://git.example.com`). Do **not** include a path — any path is automatically stripped, and `BASE_URL` is applied separately. | `http://localhost:4321` | No | | `PUBLIC_BETTER_AUTH_URL` | Client-side auth origin for multi-origin access (same rule: origin only, no path). Set this to your primary domain when you need to access the app from different origins (e.g., both IP and domain). The client will use this URL for all auth requests instead of the current browser origin. | - | No | | `BETTER_AUTH_TRUSTED_ORIGINS` | Trusted origins for authentication requests. Comma-separated list of URLs. Use this to specify additional access URLs (e.g., local IP + domain: `http://10.10.20.45:4321,https://gitea-mirror.mydomain.tld`), SSO providers, reverse proxies, etc. | - | No | +| `BETTER_AUTH_LOG_LEVEL` | Better Auth logger verbosity. Set to `debug` to surface the full SSO/OIDC sign-in and callback trace when troubleshooting authentication. Accepted values: `debug`, `info`, `warn`, `error`. (Better Auth does **not** use the `DEBUG` env var.) | `warn` | No | | `ENCRYPTION_SECRET` | Optional encryption key for tokens (generate with: `openssl rand -base64 48`) | - | No | ## HTTPS / TLS diff --git a/docs/SSO-OIDC-SETUP.md b/docs/SSO-OIDC-SETUP.md index 02f5baf..c891aa4 100644 --- a/docs/SSO-OIDC-SETUP.md +++ b/docs/SSO-OIDC-SETUP.md @@ -28,7 +28,7 @@ SSO allows your users to sign in using external identity providers like Google, #### Required Fields - **Issuer URL**: The OIDC issuer URL (e.g., `https://accounts.google.com`) -- **Domain**: The email domain for this provider (e.g., `example.com`) +- **Domain**: The email domain this provider serves (e.g., `example.com`). **This is load-bearing for account-linking trust — see [Account Linking](#account-linking) below.** Multi-domain IdPs can use a comma-separated list (`example.com,subsidiary.com`). - **Provider ID**: A unique identifier for this provider (e.g., `google-sso`) - **Client ID**: The OAuth client ID from your provider - **Client Secret**: The OAuth client secret from your provider @@ -57,6 +57,22 @@ https://your-domain.com/api/auth/sso/callback/{provider-id} Replace `{provider-id}` with your chosen Provider ID. +### Account Linking + +When a user signs in via SSO whose email matches an **existing** email/password account, Gitea Mirror will try to *link* the SSO identity to that account so the user lands on the same dashboard — instead of being bounced to the login page or creating a duplicate account. + +The trust model is **domain-scoped**: + +- Every SSO provider you register is automatically marked `domainVerified: true` for the **Domain** field you set on registration. +- An SSO sign-in is auto-linked to an existing local account **only when** the user's email address actually belongs to that registered domain. Cross-domain emails are rejected even if the provider is registered. +- Local email/password accounts in this app are never verified (no email-verification flow exists), so we deliberately turn off `requireLocalEmailVerified` on the linker — otherwise no one could ever link. + +**What this means for you:** + +- Set the **Domain** field to the email domain(s) your IdP actually issues identities for. Don't set it to `example.com` if your IdP issues `@elsewhere.com` emails — auto-linking will silently refuse them. +- If your IdP allows users to self-register or claim arbitrary emails *within the registered domain*, an attacker on that IdP can absorb a local account by claiming the same email. This is a trust decision: by registering an IdP, you're vouching for its identity model for that domain. +- Multi-domain IdPs (one Authentik serving `acme.com,acquired.io`) work fine — list every domain comma-separated in the **Domain** field. + ### Example: Google SSO Setup 1. Go to [Google Cloud Console](https://console.cloud.google.com/) @@ -99,7 +115,7 @@ Working Authentik deployments (see [#134](https://github.com/RayLabsHQ/gitea-mir Notes: - Make sure `BETTER_AUTH_URL` and (if you serve the UI from multiple origins) `BETTER_AUTH_TRUSTED_ORIGINS` point at the public URL users reach. A mismatch can surface as 500 errors after redirect. -- Authentik must report the user’s email as verified (default behavior) so Gitea Mirror can auto-link accounts. +- Set the **Domain** field to the email domain your Authentik users actually have. Auto-linking to an existing local admin only happens for emails in that domain — see [Account Linking](#account-linking) for the trust model. (Authentik's default email scope mapping returns `email_verified: False` for OIDC clients, which is why account linking is gated on the domain match here rather than the IdP's verified-email claim.) - If you created an Authentik provider before v3.8.10 you should delete it and re-add it after upgrading; older versions saved incomplete endpoint data which leads to the `url.startsWith` error explained in the Troubleshooting section. ## Setting up Header / Forward Authentication @@ -257,7 +273,8 @@ When an application requests authentication: 1. **"Invalid origin" error**: Check that your Gitea Mirror URL matches the configured redirect URI 2. **"Provider not found" error**: Ensure the provider is properly configured and enabled 3. **Redirect loop**: Verify the redirect URI in both Gitea Mirror and the SSO provider match exactly -4. **`TypeError: undefined is not an object (evaluating 'url.startsWith')`**: This indicates the stored provider configuration is missing OIDC endpoints. Delete the provider from Gitea Mirror and re-register it using the **Discover** button so authorization/token URLs are saved (see [#73](https://github.com/RayLabsHQ/gitea-mirror/issues/73) and [#122](https://github.com/RayLabsHQ/gitea-mirror/issues/122) for examples). +4. **`?error=UNKNOWN` on the homepage after a successful upstream login** (or `?error=account%20not%20linked` in development): the SSO callback succeeded but Better Auth refused to link the SSO identity to an existing local account. The most common cause is the SSO provider's registered **Domain** not matching the user's actual email domain — see [Account Linking](#account-linking). Set `BETTER_AUTH_LOG_LEVEL=debug` to see Better Auth's full callback trace (look for "User already exist but account isn't linked to ...") and confirm the diagnosis. The same symptom in production gets sanitized to `UNKNOWN` by Better Auth's error page before the redirect, which is why the visible error is opaque. +5. **`TypeError: undefined is not an object (evaluating 'url.startsWith')`**: This indicates the stored provider configuration is missing OIDC endpoints. Delete the provider from Gitea Mirror and re-register it using the **Discover** button so authorization/token URLs are saved (see [#73](https://github.com/RayLabsHQ/gitea-mirror/issues/73) and [#122](https://github.com/RayLabsHQ/gitea-mirror/issues/122) for examples). ### OIDC Provider Issues diff --git a/docs/SSO_TESTING.md b/docs/SSO_TESTING.md index 1a87676..6d523e7 100644 --- a/docs/SSO_TESTING.md +++ b/docs/SSO_TESTING.md @@ -125,11 +125,59 @@ npm start ### Debug Mode: -Enable debug logging by setting environment variable: +> **Note:** Better Auth uses its own logger and does **not** read the `DEBUG` +> environment variable (it is not based on the `debug` npm package). An older +> version of this guide suggested `DEBUG=better-auth:*` — that has no effect. + +Better Auth's logger defaults to the `warn` level, so SSO/OIDC sign-in and +callback details are hidden. Set the log level to `debug` to surface the full +trace: + ```bash -DEBUG=better-auth:* bun run dev +# Local dev +BETTER_AUTH_LOG_LEVEL=debug bun run dev ``` +```yaml +# Docker Compose +services: + gitea-mirror: + environment: + - BETTER_AUTH_LOG_LEVEL=debug +``` + +Then watch the server logs (e.g. `docker compose logs -f gitea-mirror`) while you +attempt an SSO login. Lines are prefixed with `[Better Auth]:`. Accepted values +are `debug`, `info`, `warn`, and `error`. + +#### Debugging a login that bounces back to `/login` + +If clicking the SSO button sends you to the provider and then straight back to +the login screen, the OAuth flow itself usually succeeded but **no session +cookie was persisted**. Work through these checks: + +1. **Enable `BETTER_AUTH_LOG_LEVEL=debug`** (above) and look for errors during + the `/api/auth/sso/callback/` request. +2. **Check for `?error=UNKNOWN` on the landing URL** (or `?error=account%20not%20linked` in dev). + That's Better Auth's account-linking step refusing to attach the SSO identity + to an existing email/password account. The debug log line to look for is + `User already exist but account isn't linked to `. The fix is + almost always to set the SSO provider's **Domain** field to the email domain + your users actually have — auto-linking is gated on that domain match. + See [docs/SSO-OIDC-SETUP.md#account-linking](./SSO-OIDC-SETUP.md#account-linking). +3. **Check the redirect URI** registered in your IdP exactly matches + `https:///api/auth/sso/callback/` (scheme, host, + and provider ID — no trailing slash). +4. **Confirm the session cookie is set.** In the browser DevTools → Network, + inspect the callback response for a `Set-Cookie: better-auth-session=…` + header, and DevTools → Application → Cookies for the stored cookie. Behind a + reverse proxy, ensure `BETTER_AUTH_URL` is your **external HTTPS** URL so the + cookie is issued with the correct domain and `Secure` flag, and that the + proxy forwards `X-Forwarded-Proto: https` and `X-Forwarded-Host`. +5. **A `401` on `/api/sso/applications` is unrelated** to client login — that + endpoint backs the OAuth *provider* (consent) management UI and requires an + existing session. It is not part of the Authentik/OIDC sign-in flow. + ## Testing Different Scenarios ### 1. New User Registration diff --git a/drizzle/0012_oauth_provider_migration.sql b/drizzle/0012_oauth_provider_migration.sql new file mode 100644 index 0000000..3cea9a4 --- /dev/null +++ b/drizzle/0012_oauth_provider_migration.sql @@ -0,0 +1,126 @@ +-- Migrate the OAuth/OIDC *provider* feature from the deprecated +-- `oidc-provider` plugin to `@better-auth/oauth-provider`. +-- +-- Tables: oauth_applications -> oauth_clients, oauth_access_tokens reshaped, +-- new oauth_refresh_tokens, oauth_consent -> oauth_consents, plus a `jwks` +-- table for the `jwt` plugin (id_token signing keys). +-- +-- Data preservation: +-- * Registered clients are copied from oauth_applications into oauth_clients, +-- converting the legacy comma-separated `redirect_urls` into the JSON +-- string[] (`redirect_uris`) the new adapter expects. +-- * Access tokens and consent records are NOT migrated: access tokens are +-- short-lived (and the column shape changed entirely), and consents are +-- cheaply re-granted on next authorization. Both old tables are dropped. +-- +-- NOTE: legacy client secrets were stored in plaintext, whereas the new +-- provider stores them hashed. Migrated client secrets will therefore not +-- validate as-is — affected applications must rotate their secret after +-- upgrade. + +CREATE TABLE `oauth_clients` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `client_secret` text, + `name` text, + `disabled` integer DEFAULT false, + `skip_consent` integer, + `enable_end_session` integer, + `subject_type` text, + `scopes` text, + `user_id` text, + `uri` text, + `icon` text, + `contacts` text, + `tos` text, + `policy` text, + `software_id` text, + `software_version` text, + `software_statement` text, + `redirect_uris` text NOT NULL, + `post_logout_redirect_uris` text, + `token_endpoint_auth_method` text, + `grant_types` text, + `response_types` text, + `public` integer, + `type` text, + `require_pkce` integer, + `reference_id` text, + `metadata` text, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_clients_client_id_unique` ON `oauth_clients` (`client_id`);--> statement-breakpoint +CREATE INDEX `idx_oauth_clients_client_id` ON `oauth_clients` (`client_id`);--> statement-breakpoint +CREATE INDEX `idx_oauth_clients_user_id` ON `oauth_clients` (`user_id`);--> statement-breakpoint +INSERT INTO `oauth_clients` ( + `id`, `client_id`, `client_secret`, `name`, `disabled`, `user_id`, + `redirect_uris`, `type`, `metadata`, `created_at`, `updated_at` +) +SELECT + `id`, `client_id`, `client_secret`, `name`, `disabled`, `user_id`, + '["' || replace(`redirect_urls`, ',', '","') || '"]', + `type`, `metadata`, `created_at`, `updated_at` +FROM `oauth_applications`;--> statement-breakpoint +DROP TABLE `oauth_applications`;--> statement-breakpoint +DROP TABLE `oauth_access_tokens`;--> statement-breakpoint +CREATE TABLE `oauth_access_tokens` ( + `id` text PRIMARY KEY NOT NULL, + `token` text, + `client_id` text NOT NULL, + `session_id` text, + `user_id` text, + `reference_id` text, + `refresh_id` text, + `expires_at` integer, + `created_at` integer DEFAULT (unixepoch()), + `scopes` text NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_access_tokens_token_unique` ON `oauth_access_tokens` (`token`);--> statement-breakpoint +CREATE INDEX `idx_oauth_access_tokens_token` ON `oauth_access_tokens` (`token`);--> statement-breakpoint +CREATE INDEX `idx_oauth_access_tokens_client_id` ON `oauth_access_tokens` (`client_id`);--> statement-breakpoint +CREATE INDEX `idx_oauth_access_tokens_user_id` ON `oauth_access_tokens` (`user_id`);--> statement-breakpoint +CREATE TABLE `oauth_refresh_tokens` ( + `id` text PRIMARY KEY NOT NULL, + `token` text NOT NULL, + `client_id` text NOT NULL, + `session_id` text, + `user_id` text NOT NULL, + `reference_id` text, + `expires_at` integer, + `created_at` integer DEFAULT (unixepoch()), + `revoked` integer, + `auth_time` integer, + `scopes` text NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE UNIQUE INDEX `oauth_refresh_tokens_token_unique` ON `oauth_refresh_tokens` (`token`);--> statement-breakpoint +CREATE INDEX `idx_oauth_refresh_tokens_token` ON `oauth_refresh_tokens` (`token`);--> statement-breakpoint +CREATE INDEX `idx_oauth_refresh_tokens_client_id` ON `oauth_refresh_tokens` (`client_id`);--> statement-breakpoint +CREATE INDEX `idx_oauth_refresh_tokens_user_id` ON `oauth_refresh_tokens` (`user_id`);--> statement-breakpoint +DROP TABLE `oauth_consent`;--> statement-breakpoint +CREATE TABLE `oauth_consents` ( + `id` text PRIMARY KEY NOT NULL, + `client_id` text NOT NULL, + `user_id` text, + `reference_id` text, + `scopes` text NOT NULL, + `created_at` integer DEFAULT (unixepoch()), + `updated_at` integer DEFAULT (unixepoch()), + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +CREATE INDEX `idx_oauth_consents_client_id` ON `oauth_consents` (`client_id`);--> statement-breakpoint +CREATE INDEX `idx_oauth_consents_user_id` ON `oauth_consents` (`user_id`);--> statement-breakpoint +CREATE TABLE `jwks` ( + `id` text PRIMARY KEY NOT NULL, + `public_key` text NOT NULL, + `private_key` text NOT NULL, + `created_at` integer DEFAULT (unixepoch()) NOT NULL, + `expires_at` integer +); diff --git a/drizzle/0013_slim_galactus.sql b/drizzle/0013_slim_galactus.sql new file mode 100644 index 0000000..e204dd7 --- /dev/null +++ b/drizzle/0013_slim_galactus.sql @@ -0,0 +1,35 @@ +PRAGMA foreign_keys=OFF;--> statement-breakpoint +CREATE TABLE `__new_organizations` ( + `id` text PRIMARY KEY NOT NULL, + `user_id` text NOT NULL, + `config_id` text NOT NULL, + `name` text NOT NULL, + `normalized_name` text NOT NULL, + `avatar_url` text NOT NULL, + `membership_role` text DEFAULT 'member' NOT NULL, + `is_included` integer DEFAULT true NOT NULL, + `destination_org` text, + `status` text DEFAULT 'imported' NOT NULL, + `last_mirrored` integer, + `error_message` text, + `repository_count` integer DEFAULT 0 NOT NULL, + `public_repository_count` integer, + `private_repository_count` integer, + `fork_repository_count` integer, + `created_at` integer DEFAULT (unixepoch()) NOT NULL, + `updated_at` integer DEFAULT (unixepoch()) NOT NULL, + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action, + FOREIGN KEY (`config_id`) REFERENCES `configs`(`id`) ON UPDATE no action ON DELETE no action +); +--> statement-breakpoint +INSERT INTO `__new_organizations`("id", "user_id", "config_id", "name", "normalized_name", "avatar_url", "membership_role", "is_included", "destination_org", "status", "last_mirrored", "error_message", "repository_count", "public_repository_count", "private_repository_count", "fork_repository_count", "created_at", "updated_at") SELECT "id", "user_id", "config_id", "name", "normalized_name", "avatar_url", "membership_role", "is_included", "destination_org", "status", "last_mirrored", "error_message", "repository_count", "public_repository_count", "private_repository_count", "fork_repository_count", "created_at", "updated_at" FROM `organizations`;--> statement-breakpoint +DROP TABLE `organizations`;--> statement-breakpoint +ALTER TABLE `__new_organizations` RENAME TO `organizations`;--> statement-breakpoint +PRAGMA foreign_keys=ON;--> statement-breakpoint +CREATE INDEX `idx_organizations_user_id` ON `organizations` (`user_id`);--> statement-breakpoint +CREATE INDEX `idx_organizations_config_id` ON `organizations` (`config_id`);--> statement-breakpoint +CREATE INDEX `idx_organizations_status` ON `organizations` (`status`);--> statement-breakpoint +CREATE INDEX `idx_organizations_is_included` ON `organizations` (`is_included`);--> statement-breakpoint +CREATE UNIQUE INDEX `uniq_organizations_user_normalized_name` ON `organizations` (`user_id`,`normalized_name`);--> statement-breakpoint +ALTER TABLE `sso_providers` ADD `saml_config` text;--> statement-breakpoint +ALTER TABLE `sso_providers` ADD `domain_verified` integer DEFAULT true NOT NULL; \ No newline at end of file diff --git a/drizzle/meta/0012_snapshot.json b/drizzle/meta/0012_snapshot.json new file mode 100644 index 0000000..c8f7c97 --- /dev/null +++ b/drizzle/meta/0012_snapshot.json @@ -0,0 +1,2358 @@ +{ + "id": "ca81f5e6-3b40-4b2b-9b78-9a6f3d9e1a12", + "prevId": "de932758-9842-4ae3-9fc3-65d8f2fe2bda", + "version": "6", + "dialect": "sqlite", + "tables": { + "accounts": { + "name": "accounts", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "account_id": { + "autoincrement": false, + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "autoincrement": false, + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_user_id": { + "autoincrement": false, + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token": { + "autoincrement": false, + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_token": { + "autoincrement": false, + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "password": { + "autoincrement": false, + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "id_token": { + "autoincrement": false, + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "access_token_expires_at": { + "autoincrement": false, + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "refresh_token_expires_at": { + "autoincrement": false, + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scope": { + "autoincrement": false, + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_accounts_provider": { + "name": "idx_accounts_provider", + "columns": [ + "provider_id", + "provider_user_id" + ], + "isUnique": false + }, + "idx_accounts_user_id": { + "name": "idx_accounts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_accounts_account_id": { + "name": "idx_accounts_account_id", + "columns": [ + "account_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "configs": { + "name": "configs", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "default": true, + "autoincrement": false, + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "github_config": { + "autoincrement": false, + "name": "github_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "gitea_config": { + "autoincrement": false, + "name": "gitea_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "include": { + "default": "'[\"*\"]'", + "autoincrement": false, + "name": "include", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "exclude": { + "default": "'[]'", + "autoincrement": false, + "name": "exclude", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "schedule_config": { + "autoincrement": false, + "name": "schedule_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "cleanup_config": { + "autoincrement": false, + "name": "cleanup_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "notification_config": { + "default": "'{\"enabled\":false,\"provider\":\"ntfy\",\"notifyOnSyncError\":true,\"notifyOnSyncSuccess\":false,\"notifyOnNewRepo\":false}'", + "autoincrement": false, + "name": "notification_config", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": { + "configs_user_id_users_id_fk": { + "name": "configs_user_id_users_id_fk", + "tableFrom": "configs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "events": { + "name": "events", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "channel": { + "autoincrement": false, + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "payload": { + "autoincrement": false, + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "read": { + "default": false, + "autoincrement": false, + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_events_read": { + "name": "idx_events_read", + "columns": [ + "read" + ], + "isUnique": false + }, + "idx_events_created_at": { + "name": "idx_events_created_at", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "idx_events_user_channel": { + "name": "idx_events_user_channel", + "columns": [ + "user_id", + "channel" + ], + "isUnique": false + } + }, + "foreignKeys": { + "events_user_id_users_id_fk": { + "name": "events_user_id_users_id_fk", + "tableFrom": "events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mirror_jobs": { + "name": "mirror_jobs", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "repository_id": { + "autoincrement": false, + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_name": { + "autoincrement": false, + "name": "repository_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_id": { + "autoincrement": false, + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "organization_name": { + "autoincrement": false, + "name": "organization_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "autoincrement": false, + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "default": "'imported'", + "autoincrement": false, + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message": { + "autoincrement": false, + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "timestamp": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "job_type": { + "default": "'mirror'", + "autoincrement": false, + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "batch_id": { + "autoincrement": false, + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "total_items": { + "autoincrement": false, + "name": "total_items", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "completed_items": { + "default": 0, + "autoincrement": false, + "name": "completed_items", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "item_ids": { + "autoincrement": false, + "name": "item_ids", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "completed_item_ids": { + "default": "'[]'", + "autoincrement": false, + "name": "completed_item_ids", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "in_progress": { + "default": false, + "autoincrement": false, + "name": "in_progress", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "started_at": { + "autoincrement": false, + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "completed_at": { + "autoincrement": false, + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "last_checkpoint": { + "autoincrement": false, + "name": "last_checkpoint", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_mirror_jobs_timestamp": { + "name": "idx_mirror_jobs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + }, + "idx_mirror_jobs_job_type": { + "name": "idx_mirror_jobs_job_type", + "columns": [ + "job_type" + ], + "isUnique": false + }, + "idx_mirror_jobs_in_progress": { + "name": "idx_mirror_jobs_in_progress", + "columns": [ + "in_progress" + ], + "isUnique": false + }, + "idx_mirror_jobs_batch_id": { + "name": "idx_mirror_jobs_batch_id", + "columns": [ + "batch_id" + ], + "isUnique": false + }, + "idx_mirror_jobs_user_id": { + "name": "idx_mirror_jobs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "mirror_jobs_user_id_users_id_fk": { + "name": "mirror_jobs_user_id_users_id_fk", + "tableFrom": "mirror_jobs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "autoincrement": false, + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "avatar_url": { + "autoincrement": false, + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "membership_role": { + "default": "'member'", + "autoincrement": false, + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_included": { + "default": true, + "autoincrement": false, + "name": "is_included", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "destination_org": { + "autoincrement": false, + "name": "destination_org", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "default": "'imported'", + "autoincrement": false, + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_mirrored": { + "autoincrement": false, + "name": "last_mirrored", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "autoincrement": false, + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "repository_count": { + "default": 0, + "autoincrement": false, + "name": "repository_count", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "public_repository_count": { + "autoincrement": false, + "name": "public_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "private_repository_count": { + "autoincrement": false, + "name": "private_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "fork_repository_count": { + "autoincrement": false, + "name": "fork_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "normalized_name": { + "default": "''", + "autoincrement": false, + "name": "normalized_name", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "uniq_organizations_user_normalized_name": { + "name": "uniq_organizations_user_normalized_name", + "columns": [ + "user_id", + "normalized_name" + ], + "isUnique": true + }, + "idx_organizations_is_included": { + "name": "idx_organizations_is_included", + "columns": [ + "is_included" + ], + "isUnique": false + }, + "idx_organizations_status": { + "name": "idx_organizations_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_organizations_config_id": { + "name": "idx_organizations_config_id", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "idx_organizations_user_id": { + "name": "idx_organizations_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organizations_config_id_configs_id_fk": { + "name": "organizations_config_id_configs_id_fk", + "tableFrom": "organizations", + "tableTo": "configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organizations_user_id_users_id_fk": { + "name": "organizations_user_id_users_id_fk", + "tableFrom": "organizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "ip_address": { + "autoincrement": false, + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_agent": { + "autoincrement": false, + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + }, + "idx_sessions_token": { + "name": "idx_sessions_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "sessions_token_unique": { + "name": "sessions_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "autoincrement": false, + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email_verified": { + "default": false, + "autoincrement": false, + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "image": { + "autoincrement": false, + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "username": { + "autoincrement": false, + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification_tokens": { + "name": "verification_tokens", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "identifier": { + "autoincrement": false, + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "autoincrement": false, + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_verification_tokens_identifier": { + "name": "idx_verification_tokens_identifier", + "columns": [ + "identifier" + ], + "isUnique": false + }, + "idx_verification_tokens_token": { + "name": "idx_verification_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "verification_tokens_token_unique": { + "name": "verification_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "issuer": { + "autoincrement": false, + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "domain": { + "autoincrement": false, + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "oidc_config": { + "autoincrement": false, + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider_id": { + "autoincrement": false, + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization_id": { + "autoincrement": false, + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_sso_providers_issuer": { + "name": "idx_sso_providers_issuer", + "columns": [ + "issuer" + ], + "isUnique": false + }, + "idx_sso_providers_domain": { + "name": "idx_sso_providers_domain", + "columns": [ + "domain" + ], + "isUnique": false + }, + "idx_sso_providers_provider_id": { + "name": "idx_sso_providers_provider_id", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "sso_providers_provider_id_unique": { + "name": "sso_providers_provider_id_unique", + "columns": [ + "provider_id" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verifications": { + "name": "verifications", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "identifier": { + "autoincrement": false, + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "autoincrement": false, + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_verifications_identifier": { + "name": "idx_verifications_identifier", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rate_limits": { + "name": "rate_limits", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "default": "'github'", + "autoincrement": false, + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "limit": { + "autoincrement": false, + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "remaining": { + "autoincrement": false, + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "used": { + "autoincrement": false, + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "reset": { + "autoincrement": false, + "name": "reset", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retry_after": { + "autoincrement": false, + "name": "retry_after", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "status": { + "default": "'ok'", + "autoincrement": false, + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_checked": { + "autoincrement": false, + "name": "last_checked", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_rate_limits_status": { + "name": "idx_rate_limits_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_rate_limits_user_provider": { + "name": "idx_rate_limits_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rate_limits_user_id_users_id_fk": { + "name": "rate_limits_user_id_users_id_fk", + "tableFrom": "rate_limits", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "config_id": { + "autoincrement": false, + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "full_name": { + "autoincrement": false, + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "normalized_full_name": { + "autoincrement": false, + "name": "normalized_full_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "url": { + "autoincrement": false, + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "clone_url": { + "autoincrement": false, + "name": "clone_url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "owner": { + "autoincrement": false, + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "organization": { + "autoincrement": false, + "name": "organization", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "mirrored_location": { + "default": "''", + "autoincrement": false, + "name": "mirrored_location", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_private": { + "default": false, + "autoincrement": false, + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_fork": { + "default": false, + "autoincrement": false, + "name": "is_fork", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "forked_from": { + "autoincrement": false, + "name": "forked_from", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "has_issues": { + "default": false, + "autoincrement": false, + "name": "has_issues", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_starred": { + "default": false, + "autoincrement": false, + "name": "is_starred", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "is_archived": { + "default": false, + "autoincrement": false, + "name": "is_archived", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "size": { + "default": 0, + "autoincrement": false, + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "has_lfs": { + "default": false, + "autoincrement": false, + "name": "has_lfs", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "has_submodules": { + "default": false, + "autoincrement": false, + "name": "has_submodules", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "language": { + "autoincrement": false, + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "description": { + "autoincrement": false, + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "default_branch": { + "autoincrement": false, + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "visibility": { + "default": "'public'", + "autoincrement": false, + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "default": "'imported'", + "autoincrement": false, + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_mirrored": { + "autoincrement": false, + "name": "last_mirrored", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "error_message": { + "autoincrement": false, + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "destination_org": { + "autoincrement": false, + "name": "destination_org", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "autoincrement": false, + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "imported_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_repositories_mirrored_location": { + "name": "idx_repositories_mirrored_location", + "columns": [ + "user_id", + "mirrored_location" + ], + "isUnique": false + }, + "uniq_repositories_user_normalized_full_name": { + "name": "uniq_repositories_user_normalized_full_name", + "columns": [ + "user_id", + "normalized_full_name" + ], + "isUnique": true + }, + "uniq_repositories_user_full_name": { + "name": "uniq_repositories_user_full_name", + "columns": [ + "user_id", + "full_name" + ], + "isUnique": true + }, + "idx_repositories_user_imported_at": { + "name": "idx_repositories_user_imported_at", + "columns": [ + "user_id", + "imported_at" + ], + "isUnique": false + }, + "idx_repositories_is_starred": { + "name": "idx_repositories_is_starred", + "columns": [ + "is_starred" + ], + "isUnique": false + }, + "idx_repositories_is_fork": { + "name": "idx_repositories_is_fork", + "columns": [ + "is_fork" + ], + "isUnique": false + }, + "idx_repositories_organization": { + "name": "idx_repositories_organization", + "columns": [ + "organization" + ], + "isUnique": false + }, + "idx_repositories_owner": { + "name": "idx_repositories_owner", + "columns": [ + "owner" + ], + "isUnique": false + }, + "idx_repositories_status": { + "name": "idx_repositories_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_repositories_config_id": { + "name": "idx_repositories_config_id", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "idx_repositories_user_id": { + "name": "idx_repositories_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "repositories_config_id_configs_id_fk": { + "name": "repositories_config_id_configs_id_fk", + "tableFrom": "repositories", + "tableTo": "configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "repositories_user_id_users_id_fk": { + "name": "repositories_user_id_users_id_fk", + "tableFrom": "repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_clients": { + "name": "oauth_clients", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "autoincrement": false, + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_secret": { + "autoincrement": false, + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "name": { + "autoincrement": false, + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "disabled": { + "default": false, + "autoincrement": false, + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "skip_consent": { + "autoincrement": false, + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "enable_end_session": { + "autoincrement": false, + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "subject_type": { + "autoincrement": false, + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "autoincrement": false, + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "uri": { + "autoincrement": false, + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "icon": { + "autoincrement": false, + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "contacts": { + "autoincrement": false, + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tos": { + "autoincrement": false, + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "policy": { + "autoincrement": false, + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_id": { + "autoincrement": false, + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_version": { + "autoincrement": false, + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "software_statement": { + "autoincrement": false, + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "redirect_uris": { + "autoincrement": false, + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "post_logout_redirect_uris": { + "autoincrement": false, + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "token_endpoint_auth_method": { + "autoincrement": false, + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "grant_types": { + "autoincrement": false, + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "response_types": { + "autoincrement": false, + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "public": { + "autoincrement": false, + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "type": { + "autoincrement": false, + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "require_pkce": { + "autoincrement": false, + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "autoincrement": false, + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "metadata": { + "autoincrement": false, + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_oauth_clients_user_id": { + "name": "idx_oauth_clients_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_oauth_clients_client_id": { + "name": "idx_oauth_clients_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + } + }, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_access_tokens": { + "name": "oauth_access_tokens", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "client_id": { + "autoincrement": false, + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "autoincrement": false, + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "autoincrement": false, + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "refresh_id": { + "autoincrement": false, + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "autoincrement": false, + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_oauth_access_tokens_user_id": { + "name": "idx_oauth_access_tokens_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_oauth_access_tokens_client_id": { + "name": "idx_oauth_access_tokens_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_access_tokens_token": { + "name": "idx_oauth_access_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "token": { + "autoincrement": false, + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "client_id": { + "autoincrement": false, + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "session_id": { + "autoincrement": false, + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "reference_id": { + "autoincrement": false, + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "revoked": { + "autoincrement": false, + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "auth_time": { + "autoincrement": false, + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "autoincrement": false, + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_oauth_refresh_tokens_user_id": { + "name": "idx_oauth_refresh_tokens_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_oauth_refresh_tokens_client_id": { + "name": "idx_oauth_refresh_tokens_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_refresh_tokens_token": { + "name": "idx_oauth_refresh_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "oauth_refresh_tokens_token_unique": { + "name": "oauth_refresh_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + } + }, + "foreignKeys": { + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_consents": { + "name": "oauth_consents", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "client_id": { + "autoincrement": false, + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "autoincrement": false, + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "reference_id": { + "autoincrement": false, + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "scopes": { + "autoincrement": false, + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "updated_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": { + "idx_oauth_consents_user_id": { + "name": "idx_oauth_consents_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_oauth_consents_client_id": { + "name": "idx_oauth_consents_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "autoincrement": false, + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "public_key": { + "autoincrement": false, + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "private_key": { + "autoincrement": false, + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "default": "(unixepoch())", + "autoincrement": false, + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "autoincrement": false, + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false + } + }, + "compositePrimaryKeys": {}, + "indexes": {}, + "foreignKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0013_snapshot.json b/drizzle/meta/0013_snapshot.json new file mode 100644 index 0000000..ad7a094 --- /dev/null +++ b/drizzle/meta/0013_snapshot.json @@ -0,0 +1,2375 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "6beb5116-f397-4dcb-ac73-f2b81079f61d", + "prevId": "ca81f5e6-3b40-4b2b-9b78-9a6f3d9e1a12", + "tables": { + "accounts": { + "name": "accounts", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "account_id": { + "name": "account_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_user_id": { + "name": "provider_user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token": { + "name": "access_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token": { + "name": "refresh_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "id_token": { + "name": "id_token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "access_token_expires_at": { + "name": "access_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_token_expires_at": { + "name": "refresh_token_expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scope": { + "name": "scope", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_accounts_account_id": { + "name": "idx_accounts_account_id", + "columns": [ + "account_id" + ], + "isUnique": false + }, + "idx_accounts_user_id": { + "name": "idx_accounts_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_accounts_provider": { + "name": "idx_accounts_provider", + "columns": [ + "provider_id", + "provider_user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "accounts_user_id_users_id_fk": { + "name": "accounts_user_id_users_id_fk", + "tableFrom": "accounts", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "configs": { + "name": "configs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_active": { + "name": "is_active", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "github_config": { + "name": "github_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "gitea_config": { + "name": "gitea_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "include": { + "name": "include", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[\"*\"]'" + }, + "exclude": { + "name": "exclude", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'[]'" + }, + "schedule_config": { + "name": "schedule_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "cleanup_config": { + "name": "cleanup_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "notification_config": { + "name": "notification_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'{\"enabled\":false,\"provider\":\"ntfy\",\"notifyOnSyncError\":true,\"notifyOnSyncSuccess\":false,\"notifyOnNewRepo\":false}'" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": {}, + "foreignKeys": { + "configs_user_id_users_id_fk": { + "name": "configs_user_id_users_id_fk", + "tableFrom": "configs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "events": { + "name": "events", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "channel": { + "name": "channel", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "payload": { + "name": "payload", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "read": { + "name": "read", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_events_user_channel": { + "name": "idx_events_user_channel", + "columns": [ + "user_id", + "channel" + ], + "isUnique": false + }, + "idx_events_created_at": { + "name": "idx_events_created_at", + "columns": [ + "created_at" + ], + "isUnique": false + }, + "idx_events_read": { + "name": "idx_events_read", + "columns": [ + "read" + ], + "isUnique": false + } + }, + "foreignKeys": { + "events_user_id_users_id_fk": { + "name": "events_user_id_users_id_fk", + "tableFrom": "events", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "jwks": { + "name": "jwks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "public_key": { + "name": "public_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "private_key": { + "name": "private_key", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "mirror_jobs": { + "name": "mirror_jobs", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repository_name": { + "name": "repository_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "organization_name": { + "name": "organization_name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "details": { + "name": "details", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'imported'" + }, + "message": { + "name": "message", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "timestamp": { + "name": "timestamp", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "job_type": { + "name": "job_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'mirror'" + }, + "batch_id": { + "name": "batch_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "total_items": { + "name": "total_items", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_items": { + "name": "completed_items", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": 0 + }, + "item_ids": { + "name": "item_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_item_ids": { + "name": "completed_item_ids", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "'[]'" + }, + "in_progress": { + "name": "in_progress", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "started_at": { + "name": "started_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_checkpoint": { + "name": "last_checkpoint", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "idx_mirror_jobs_user_id": { + "name": "idx_mirror_jobs_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_mirror_jobs_batch_id": { + "name": "idx_mirror_jobs_batch_id", + "columns": [ + "batch_id" + ], + "isUnique": false + }, + "idx_mirror_jobs_in_progress": { + "name": "idx_mirror_jobs_in_progress", + "columns": [ + "in_progress" + ], + "isUnique": false + }, + "idx_mirror_jobs_job_type": { + "name": "idx_mirror_jobs_job_type", + "columns": [ + "job_type" + ], + "isUnique": false + }, + "idx_mirror_jobs_timestamp": { + "name": "idx_mirror_jobs_timestamp", + "columns": [ + "timestamp" + ], + "isUnique": false + } + }, + "foreignKeys": { + "mirror_jobs_user_id_users_id_fk": { + "name": "mirror_jobs_user_id_users_id_fk", + "tableFrom": "mirror_jobs", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_access_tokens": { + "name": "oauth_access_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "refresh_id": { + "name": "refresh_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_access_tokens_token_unique": { + "name": "oauth_access_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_oauth_access_tokens_token": { + "name": "idx_oauth_access_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "idx_oauth_access_tokens_client_id": { + "name": "idx_oauth_access_tokens_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_access_tokens_user_id": { + "name": "idx_oauth_access_tokens_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_access_tokens_user_id_users_id_fk": { + "name": "oauth_access_tokens_user_id_users_id_fk", + "tableFrom": "oauth_access_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_clients": { + "name": "oauth_clients", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_secret": { + "name": "client_secret", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "disabled": { + "name": "disabled", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "skip_consent": { + "name": "skip_consent", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "enable_end_session": { + "name": "enable_end_session", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "subject_type": { + "name": "subject_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "uri": { + "name": "uri", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon": { + "name": "icon", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "contacts": { + "name": "contacts", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "tos": { + "name": "tos", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "policy": { + "name": "policy", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_id": { + "name": "software_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_version": { + "name": "software_version", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "software_statement": { + "name": "software_statement", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "redirect_uris": { + "name": "redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "post_logout_redirect_uris": { + "name": "post_logout_redirect_uris", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "token_endpoint_auth_method": { + "name": "token_endpoint_auth_method", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "grant_types": { + "name": "grant_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "response_types": { + "name": "response_types", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "public": { + "name": "public", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "require_pkce": { + "name": "require_pkce", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "oauth_clients_client_id_unique": { + "name": "oauth_clients_client_id_unique", + "columns": [ + "client_id" + ], + "isUnique": true + }, + "idx_oauth_clients_client_id": { + "name": "idx_oauth_clients_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_clients_user_id": { + "name": "idx_oauth_clients_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_clients_user_id_users_id_fk": { + "name": "oauth_clients_user_id_users_id_fk", + "tableFrom": "oauth_clients", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_consents": { + "name": "oauth_consents", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_oauth_consents_client_id": { + "name": "idx_oauth_consents_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_consents_user_id": { + "name": "idx_oauth_consents_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_consents_user_id_users_id_fk": { + "name": "oauth_consents_user_id_users_id_fk", + "tableFrom": "oauth_consents", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "oauth_refresh_tokens": { + "name": "oauth_refresh_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "client_id": { + "name": "client_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "session_id": { + "name": "session_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reference_id": { + "name": "reference_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "(unixepoch())" + }, + "revoked": { + "name": "revoked", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "auth_time": { + "name": "auth_time", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "scopes": { + "name": "scopes", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "oauth_refresh_tokens_token_unique": { + "name": "oauth_refresh_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_oauth_refresh_tokens_token": { + "name": "idx_oauth_refresh_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "idx_oauth_refresh_tokens_client_id": { + "name": "idx_oauth_refresh_tokens_client_id", + "columns": [ + "client_id" + ], + "isUnique": false + }, + "idx_oauth_refresh_tokens_user_id": { + "name": "idx_oauth_refresh_tokens_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "oauth_refresh_tokens_user_id_users_id_fk": { + "name": "oauth_refresh_tokens_user_id_users_id_fk", + "tableFrom": "oauth_refresh_tokens", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "normalized_name": { + "name": "normalized_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "membership_role": { + "name": "membership_role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'member'" + }, + "is_included": { + "name": "is_included", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "destination_org": { + "name": "destination_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'imported'" + }, + "last_mirrored": { + "name": "last_mirrored", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "repository_count": { + "name": "repository_count", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "public_repository_count": { + "name": "public_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "private_repository_count": { + "name": "private_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "fork_repository_count": { + "name": "fork_repository_count", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_organizations_user_id": { + "name": "idx_organizations_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_organizations_config_id": { + "name": "idx_organizations_config_id", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "idx_organizations_status": { + "name": "idx_organizations_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_organizations_is_included": { + "name": "idx_organizations_is_included", + "columns": [ + "is_included" + ], + "isUnique": false + }, + "uniq_organizations_user_normalized_name": { + "name": "uniq_organizations_user_normalized_name", + "columns": [ + "user_id", + "normalized_name" + ], + "isUnique": true + } + }, + "foreignKeys": { + "organizations_user_id_users_id_fk": { + "name": "organizations_user_id_users_id_fk", + "tableFrom": "organizations", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "organizations_config_id_configs_id_fk": { + "name": "organizations_config_id_configs_id_fk", + "tableFrom": "organizations", + "tableTo": "configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "rate_limits": { + "name": "rate_limits", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'github'" + }, + "limit": { + "name": "limit", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "remaining": { + "name": "remaining", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "used": { + "name": "used", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "reset": { + "name": "reset", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "retry_after": { + "name": "retry_after", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'ok'" + }, + "last_checked": { + "name": "last_checked", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_rate_limits_user_provider": { + "name": "idx_rate_limits_user_provider", + "columns": [ + "user_id", + "provider" + ], + "isUnique": false + }, + "idx_rate_limits_status": { + "name": "idx_rate_limits_status", + "columns": [ + "status" + ], + "isUnique": false + } + }, + "foreignKeys": { + "rate_limits_user_id_users_id_fk": { + "name": "rate_limits_user_id_users_id_fk", + "tableFrom": "rate_limits", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "repositories": { + "name": "repositories", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_id": { + "name": "config_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "full_name": { + "name": "full_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "normalized_full_name": { + "name": "normalized_full_name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "clone_url": { + "name": "clone_url", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "owner": { + "name": "owner", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization": { + "name": "organization", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "mirrored_location": { + "name": "mirrored_location", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": "''" + }, + "is_private": { + "name": "is_private", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_fork": { + "name": "is_fork", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "forked_from": { + "name": "forked_from", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "has_issues": { + "name": "has_issues", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_starred": { + "name": "is_starred", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "is_archived": { + "name": "is_archived", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "size": { + "name": "size", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": 0 + }, + "has_lfs": { + "name": "has_lfs", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "has_submodules": { + "name": "has_submodules", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "language": { + "name": "language", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "visibility": { + "name": "visibility", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'public'" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "'imported'" + }, + "last_mirrored": { + "name": "last_mirrored", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "error_message": { + "name": "error_message", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "destination_org": { + "name": "destination_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "metadata": { + "name": "metadata", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "imported_at": { + "name": "imported_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_repositories_user_id": { + "name": "idx_repositories_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_repositories_config_id": { + "name": "idx_repositories_config_id", + "columns": [ + "config_id" + ], + "isUnique": false + }, + "idx_repositories_status": { + "name": "idx_repositories_status", + "columns": [ + "status" + ], + "isUnique": false + }, + "idx_repositories_owner": { + "name": "idx_repositories_owner", + "columns": [ + "owner" + ], + "isUnique": false + }, + "idx_repositories_organization": { + "name": "idx_repositories_organization", + "columns": [ + "organization" + ], + "isUnique": false + }, + "idx_repositories_is_fork": { + "name": "idx_repositories_is_fork", + "columns": [ + "is_fork" + ], + "isUnique": false + }, + "idx_repositories_is_starred": { + "name": "idx_repositories_is_starred", + "columns": [ + "is_starred" + ], + "isUnique": false + }, + "idx_repositories_user_imported_at": { + "name": "idx_repositories_user_imported_at", + "columns": [ + "user_id", + "imported_at" + ], + "isUnique": false + }, + "uniq_repositories_user_full_name": { + "name": "uniq_repositories_user_full_name", + "columns": [ + "user_id", + "full_name" + ], + "isUnique": true + }, + "uniq_repositories_user_normalized_full_name": { + "name": "uniq_repositories_user_normalized_full_name", + "columns": [ + "user_id", + "normalized_full_name" + ], + "isUnique": true + }, + "idx_repositories_mirrored_location": { + "name": "idx_repositories_mirrored_location", + "columns": [ + "user_id", + "mirrored_location" + ], + "isUnique": false + } + }, + "foreignKeys": { + "repositories_user_id_users_id_fk": { + "name": "repositories_user_id_users_id_fk", + "tableFrom": "repositories", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + }, + "repositories_config_id_configs_id_fk": { + "name": "repositories_config_id_configs_id_fk", + "tableFrom": "repositories", + "tableTo": "configs", + "columnsFrom": [ + "config_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sessions": { + "name": "sessions", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "ip_address": { + "name": "ip_address", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "user_agent": { + "name": "user_agent", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "sessions_token_unique": { + "name": "sessions_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_sessions_user_id": { + "name": "idx_sessions_user_id", + "columns": [ + "user_id" + ], + "isUnique": false + }, + "idx_sessions_token": { + "name": "idx_sessions_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "idx_sessions_expires_at": { + "name": "idx_sessions_expires_at", + "columns": [ + "expires_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "no action", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "sso_providers": { + "name": "sso_providers", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "issuer": { + "name": "issuer", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "domain": { + "name": "domain", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "oidc_config": { + "name": "oidc_config", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "saml_config": { + "name": "saml_config", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "domain_verified": { + "name": "domain_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": true + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "sso_providers_provider_id_unique": { + "name": "sso_providers_provider_id_unique", + "columns": [ + "provider_id" + ], + "isUnique": true + }, + "idx_sso_providers_provider_id": { + "name": "idx_sso_providers_provider_id", + "columns": [ + "provider_id" + ], + "isUnique": false + }, + "idx_sso_providers_domain": { + "name": "idx_sso_providers_domain", + "columns": [ + "domain" + ], + "isUnique": false + }, + "idx_sso_providers_issuer": { + "name": "idx_sso_providers_issuer", + "columns": [ + "issuer" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email_verified": { + "name": "email_verified", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": false + }, + "image": { + "name": "image", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "username": { + "name": "username", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verification_tokens": { + "name": "verification_tokens", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "verification_tokens_token_unique": { + "name": "verification_tokens_token_unique", + "columns": [ + "token" + ], + "isUnique": true + }, + "idx_verification_tokens_token": { + "name": "idx_verification_tokens_token", + "columns": [ + "token" + ], + "isUnique": false + }, + "idx_verification_tokens_identifier": { + "name": "idx_verification_tokens_identifier", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "verifications": { + "name": "verifications", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "expires_at": { + "name": "expires_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false, + "default": "(unixepoch())" + } + }, + "indexes": { + "idx_verifications_identifier": { + "name": "idx_verifications_identifier", + "columns": [ + "identifier" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index b6d8e08..06adde3 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -85,6 +85,20 @@ "when": 1774058400000, "tag": "0011_notification_config", "breakpoints": true + }, + { + "idx": 12, + "version": "6", + "when": 1774062000000, + "tag": "0012_oauth_provider_migration", + "breakpoints": true + }, + { + "idx": 13, + "version": "6", + "when": 1780377747526, + "tag": "0013_slim_galactus", + "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/package.json b/package.json index 0903478..c40470e 100644 --- a/package.json +++ b/package.json @@ -58,6 +58,7 @@ "@astrojs/mdx": "5.0.0", "@astrojs/node": "10.0.1", "@astrojs/react": "^5.0.0", + "@better-auth/oauth-provider": "1.6.11", "@better-auth/sso": "1.6.11", "@octokit/plugin-throttling": "^11.0.3", "@octokit/rest": "^22.0.1", diff --git a/scripts/validate-migrations.ts b/scripts/validate-migrations.ts index 7be240f..f86aa65 100644 --- a/scripts/validate-migrations.ts +++ b/scripts/validate-migrations.ts @@ -188,6 +188,108 @@ function verify0011Migration(db: any) { assert(parsed.provider === "ntfy", "Expected default notification_config.provider to be 'ntfy'"); } +function seedPre0012Database(db: any) { + // The harness has already run migrations 0000-0011, so the legacy + // oidc-provider tables exist. Seed a registered client (with the legacy + // comma-separated redirect_urls format) plus the related token/consent rows + // to exercise the create/transform/drop paths in 0012. + db.run("INSERT INTO users (id, email, username, name) VALUES ('u1', 'u1@example.com', 'user1', 'User One')"); + db.run("INSERT INTO oauth_applications (id, client_id, client_secret, name, redirect_urls, type, disabled, user_id) VALUES ('app1', 'client-1', 'secret-1', 'Example App', 'https://example.com/callback,https://example.com/cb2', 'web', false, 'u1')"); + db.run("INSERT INTO oauth_access_tokens (id, access_token, refresh_token, access_token_expires_at, refresh_token_expires_at, client_id, user_id, scopes) VALUES ('oat1', 'tok', 'rtok', 7000, 8000, 'client-1', 'u1', '[\"repo\"]')"); + db.run("INSERT INTO oauth_consent (id, user_id, client_id, scopes, consent_given) VALUES ('cons1', 'u1', 'client-1', '[\"repo\"]', true)"); +} + +function verify0012Migration(db: any) { + // Old provider tables are dropped. + for (const table of ["oauth_applications", "oauth_consent"]) { + const row = db + .query("SELECT name FROM sqlite_master WHERE type='table' AND name = ?") + .get(table) as { name: string } | null; + assert(!row, `Expected ${table} table to be dropped after migration`); + } + + // New provider tables exist. + for (const table of ["oauth_clients", "oauth_access_tokens", "oauth_refresh_tokens", "oauth_consents", "jwks"]) { + const row = db + .query("SELECT name FROM sqlite_master WHERE type='table' AND name = ?") + .get(table) as { name: string } | null; + assert(row, `Expected ${table} table to exist after migration`); + } + + // The registered client is preserved and its redirect URIs converted from + // the legacy comma-separated string into a JSON string[]. + const client = db + .query("SELECT client_id, client_secret, name, redirect_uris, type, user_id FROM oauth_clients WHERE id = 'app1'") + .get() as { client_id: string; client_secret: string; name: string; redirect_uris: string; type: string; user_id: string } | null; + assert(client, "Expected migrated oauth_clients row for app1"); + assert(client.client_id === "client-1", "Expected client_id to be preserved"); + assert(client.name === "Example App", "Expected client name to be preserved"); + assert(client.user_id === "u1", "Expected owner user_id to be preserved"); + + const uris = JSON.parse(client.redirect_uris); + assert( + Array.isArray(uris) && uris.length === 2 && uris[0] === "https://example.com/callback" && uris[1] === "https://example.com/cb2", + `Expected redirect_uris to be a JSON array of the two callbacks, got ${client.redirect_uris}`, + ); + + // The reshaped tables accept the new column layout. + db.run("INSERT INTO oauth_clients (id, client_id, redirect_uris) VALUES ('app2', 'client-2', '[\"https://example.com/cb\"]')"); + db.run("INSERT INTO oauth_refresh_tokens (id, token, client_id, user_id, scopes) VALUES ('rt1', 'refresh-1', 'client-2', 'u1', '[\"openid\"]')"); + db.run("INSERT INTO oauth_access_tokens (id, token, client_id, user_id, scopes) VALUES ('at1', 'access-1', 'client-2', 'u1', '[\"openid\"]')"); + db.run("INSERT INTO oauth_consents (id, client_id, user_id, scopes) VALUES ('co1', 'client-2', 'u1', '[\"openid\"]')"); + db.run("INSERT INTO jwks (id, public_key, private_key) VALUES ('jwk1', 'public', 'private')"); +} + +function seedPre0013Database(db: any) { + // Migrations 0000-0012 have run, so sso_providers lacks samlConfig / + // domainVerified and the organizations table still carries the inherited + // DEFAULT '' on normalized_name from 0007. Seed both so the table-rebuild + // and the column-adds can be verified end-to-end. + db.run("INSERT INTO users (id, email, username, name) VALUES ('u-sso', 'sso@example.com', 'sso', 'SSO User')"); + db.run("INSERT INTO configs (id, user_id, name, is_active, github_config, gitea_config, schedule_config, cleanup_config) VALUES ('cfg-pre13', 'u-sso', 'Default', 1, '{}', '{}', '{}', '{}')"); + db.run("INSERT INTO sso_providers (id, issuer, domain, oidc_config, user_id, provider_id) VALUES ('sso-pre13', 'https://idp.example.com', 'example.com', '{\"clientId\":\"x\"}', 'u-sso', 'idp-pre13')"); + db.run("INSERT INTO organizations (id, user_id, config_id, name, avatar_url, normalized_name) VALUES ('org-pre13', 'u-sso', 'cfg-pre13', 'Example', 'https://example.com/a.png', 'example')"); +} + +function verify0013Migration(db: any) { + // New columns on sso_providers. + const ssoCols = db + .query("PRAGMA table_info(sso_providers)") + .all() as Array<{ name: string; notnull: number; dflt_value: string | null }>; + const saml = ssoCols.find((c) => c.name === "saml_config"); + const domainVerified = ssoCols.find((c) => c.name === "domain_verified"); + assert(saml, "Expected sso_providers.saml_config column to exist"); + assert(saml.notnull === 0, "Expected saml_config to be nullable"); + assert(domainVerified, "Expected sso_providers.domain_verified column to exist"); + assert(domainVerified.notnull === 1, "Expected domain_verified to be NOT NULL"); + assert( + domainVerified.dflt_value === "true", + `Expected domain_verified DEFAULT true, got ${domainVerified.dflt_value}`, + ); + + // Pre-existing SSO row picked up the default (1 = true) on domain_verified. + const ssoRow = db + .query("SELECT provider_id, saml_config, domain_verified FROM sso_providers WHERE id = 'sso-pre13'") + .get() as { provider_id: string; saml_config: string | null; domain_verified: number } | null; + assert(ssoRow, "Expected pre-existing OIDC provider row to survive migration"); + assert(ssoRow.saml_config === null, `Expected saml_config NULL, got ${ssoRow.saml_config}`); + assert(ssoRow.domain_verified === 1, `Expected domain_verified=1, got ${ssoRow.domain_verified}`); + + // Organizations rebuild preserved the seeded row and dropped the inherited + // DEFAULT '' on normalized_name (drizzle reconciles to schema.ts). + const orgRow = db + .query("SELECT id, normalized_name FROM organizations WHERE id = 'org-pre13'") + .get() as { id: string; normalized_name: string } | null; + assert(orgRow, "Expected pre-existing organization row to survive table rebuild"); + assert(orgRow.normalized_name === "example", `Expected organization normalized_name preserved, got ${orgRow.normalized_name}`); + const orgCols = db + .query("PRAGMA table_info(organizations)") + .all() as Array<{ name: string; dflt_value: string | null }>; + const normName = orgCols.find((c) => c.name === "normalized_name"); + assert(normName, "Expected organizations.normalized_name column to exist"); + assert(normName.dflt_value === null, `Expected normalized_name to have no default, got ${normName.dflt_value}`); +} + const latestUpgradeFixtures: Record = { "0009_nervous_tyger_tiger": { seed: seedPre0009Database, @@ -201,6 +303,14 @@ const latestUpgradeFixtures: Record = { seed: seedPre0011Database, verify: verify0011Migration, }, + "0012_oauth_provider_migration": { + seed: seedPre0012Database, + verify: verify0012Migration, + }, + "0013_slim_galactus": { + seed: seedPre0013Database, + verify: verify0013Migration, + }, }; function lintMigrations(selectedMigrations: Migration[]) { diff --git a/src/components/oauth/ConsentPage.tsx b/src/components/oauth/ConsentPage.tsx index f41a7e8..7f85fc8 100644 --- a/src/components/oauth/ConsentPage.tsx +++ b/src/components/oauth/ConsentPage.tsx @@ -90,41 +90,29 @@ export default function ConsentPage() { const handleConsent = async (accept: boolean) => { setIsSubmitting(true); try { + // The OAuth provider redirected here with the authorization request in + // the query string (client_id, scope, code). Hand that back via + // `oauth_query` so the provider can resume the flow. It validates the + // redirect URI server-side and returns the URL to navigate to — either + // with an authorization code (accept) or an error (deny). + const oauthQuery = window.location.search.replace(/^\?/, ''); const result = await authClient.oauth2.consent({ accept, + oauth_query: oauthQuery, + scope: Array.from(selectedScopes).join(' '), }); if (result.error) { throw new Error(result.error.message || 'Consent failed'); } - // The consent method should handle the redirect - if (!accept) { - // If denied, redirect back to the application with error - const params = new URLSearchParams(window.location.search); - const redirectUri = params.get('redirect_uri'); - - if (redirectUri && application) { - // Validate redirect URI against authorized URIs - const authorizedUris = parseRedirectUris(application.redirectURLs); - - if (isValidRedirectUri(redirectUri, authorizedUris)) { - try { - // Parse and reconstruct the URL to ensure it's safe - const url = new URL(redirectUri); - url.searchParams.set('error', 'access_denied'); - - // Safe to redirect - URI has been validated and sanitized - window.location.href = url.toString(); - } catch (e) { - console.error('Failed to parse redirect URI:', e); - setError('Invalid redirect URI'); - } - } else { - console.error('Unauthorized redirect URI:', redirectUri); - setError('Invalid redirect URI'); - } - } + const redirectUri = (result.data as { redirect_uri?: string } | undefined)?.redirect_uri; + if (redirectUri) { + // The redirect URI is produced and validated server-side. + window.location.href = redirectUri; + } else if (!accept) { + // No redirect target returned on denial — return to the app. + window.location.href = '/'; } } catch (error) { showErrorToast(error, toast); diff --git a/src/lib/auth-client.ts b/src/lib/auth-client.ts index c30612b..be3a925 100644 --- a/src/lib/auth-client.ts +++ b/src/lib/auth-client.ts @@ -1,6 +1,6 @@ import "@/lib/polyfills/buffer"; import { createAuthClient } from "better-auth/react"; -import { oidcClient } from "better-auth/client/plugins"; +import { oauthProviderClient } from "@better-auth/oauth-provider/client"; import { ssoClient } from "@better-auth/sso/client"; import type { Session as BetterAuthSession, User as BetterAuthUser } from "better-auth"; import { withBase } from "@/lib/base-path"; @@ -41,7 +41,7 @@ export const authClient = createAuthClient({ })(), basePath: withBase('/api/auth'), // Explicitly set the base path plugins: [ - oidcClient(), + oauthProviderClient(), ssoClient(), ], }); diff --git a/src/lib/auth.ts b/src/lib/auth.ts index 8460862..5814a3a 100644 --- a/src/lib/auth.ts +++ b/src/lib/auth.ts @@ -1,6 +1,7 @@ import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { oidcProvider } from "better-auth/plugins"; +import { jwt } from "better-auth/plugins"; +import { oauthProvider } from "@better-auth/oauth-provider"; import { sso } from "@better-auth/sso"; import { db, users } from "./db"; import * as schema from "./db/schema"; @@ -74,6 +75,23 @@ export async function resolveTrustedOrigins(request?: Request): Promise