We couldn't check whether you can manage roles.
++ Checking your access… +
+ )} + +diff --git a/.env.example b/.env.example
index e32b7e1..04c77ef 100644
--- a/.env.example
+++ b/.env.example
@@ -18,7 +18,11 @@ BETTER_AUTH_SECRET=replace-with-a-random-secret-at-least-32-characters
# PN_ENTRA_TENANT_ID=
# Microsoft Entra security group used by the backend for PN members (the Soci group).
PN_ENTRA_MEMBER_GROUP_ID=1c68dbb8-4ac3-4569-a886-283b5a825cbd
-# Membership is checked with Microsoft Graph again after this interval.
+# Microsoft Entra security group whose direct members hold the built-in Direttivo role.
+# Unset: nobody is inferred as Direttivo.
+# PN_ENTRA_DIRETTIVO_GROUP_ID=
+# Lifetime of persisted sign-in evidence. Authorization independently rechecks Graph
+# using a fixed maximum 60-second cache; this setting cannot extend RBAC access.
PN_ENTRA_MEMBER_REFRESH_HOURS=24
# Google login.
@@ -36,9 +40,10 @@ PN_ENTRA_MEMBER_REFRESH_HOURS=24
AZURE_EMAIL_SENDER=noreply@polinetwork.org
STUDENT_VERIFICATION_TTL_DAYS=365
-# Who may manage OIDC clients at /applications. By default every signed-in PN Entra
-# account can. Set this to a stricter Microsoft Entra group (object ID) to limit it
-# to that group's direct members; the PN_ENTRA app checks it through Graph.
+# Who holds the built-in Master Admin role, which carries every permission. Configure
+# this Microsoft Entra administrators group (object ID) or a nonempty allowlist below.
+# Missing both stops startup. Group membership is verified by the PN_ENTRA app
+# through Graph. Everyone else is administered through roles at /access.
# PN_ENTRA_OIDC_ADMIN_GROUP_ID=
-# Comma-separated local user IDs that may always manage OIDC clients (break-glass).
+# Comma-separated local user IDs that are always Master Admin (break-glass).
IDP_ADMIN_USER_IDS=
diff --git a/CLAUDE.md b/CLAUDE.md
new file mode 100644
index 0000000..43c994c
--- /dev/null
+++ b/CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/Dockerfile b/Dockerfile
index cffb908..f714482 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -63,6 +63,7 @@ COPY --from=build --chown=node:node /app/.output ./.output
COPY --from=build --chown=node:node /app/drizzle ./drizzle
COPY --from=build --chown=node:node /app/scripts/migrate.mjs ./scripts/migrate.mjs
COPY --from=build --chown=node:node /app/scripts/start.mjs ./scripts/start.mjs
+COPY --from=build --chown=node:node /app/scripts/security-config.mjs ./scripts/security-config.mjs
USER node
EXPOSE 3000
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
diff --git a/README.md b/README.md
index 0f51163..6696d0d 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ A standalone TanStack Start and Better Auth identity provider. The backend remai
Use Node and pnpm through Vite+.
1. Run `vp install`.
-2. Copy `.env.example` to `.env.local`, set a random secret, and point the `DB_*` variables at a **new, separate PostgreSQL database**.
+2. Copy `.env.example` to `.env.local`, set a random secret, point `DB_*` at a **new, separate PostgreSQL database**, and configure an explicit admin group or `IDP_ADMIN_USER_IDS` bootstrap allowlist.
3. Set `BETTER_AUTH_URL=http://localhost:3000` for local development.
4. Run `vp run db:migrate` to apply the checked-in migration to that database.
5. Run `vp run dev` and open the origin set in `BETTER_AUTH_URL`.
@@ -18,10 +18,37 @@ The included `Dockerfile` builds the app and runs the same migration-first start
Google and PoliNetwork Entra create accounts. Once signed in, users can add a passkey from the account page and use it for future logins. Signed-out visitors see a login form with configured providers and passkey sign-in. Email/password login is disabled. The server rejects direct Telegram sign-in requests and protects the last Google or PoliNetwork Entra account from being disconnected, including when passkeys or verifier accounts remain linked.
-Passkeys require the checked-in `0003` database migration. Run `vp run db:migrate` before using them. Their relying-party ID and origin come from `BETTER_AUTH_URL`; use that exact origin in your browser, with HTTPS in production or localhost in development. Register a passkey after signing in with Google or PoliNetwork Entra. The account page lists and removes registered passkeys.
+Roles and permissions require the checked-in `0004` through `0007` migrations, which also move each account's single proven state into a list so one Entra identity can prove both Socio and Direttivo. `0004` carries the old `state` column into the new `states` list and seeds the built-in roles before `0005` drops it, so apply them in order and never `0005` alone. `0006` adds Master Admin and the `idp:*` permissions. `0007` adds immutable RBAC audit history and rejects/quarantines unsafe managed-role links. Passkeys require the checked-in `0003` database migration. Run `vp run db:migrate` before using them. Their relying-party ID and origin come from `BETTER_AUTH_URL`; use that exact origin in your browser, with HTTPS in production or localhost in development. Register a passkey after signing in with Google or PoliNetwork Entra. The account page lists and removes registered passkeys.
New registrations send `PoliNetwork Auth` as the relying-party name. The username uses the user's real email, then an email from stored Google or Microsoft ID-token claims, and falls back to the user's name if neither is available. These claims are display metadata only. Passkey labels use the authenticator's AAGUID to recognize password managers such as 1Password; unknown authenticators display `Passkey`. Existing default labels are resolved when listed, while custom names are preserved. Password managers control their own vault item titles and may still show `localhost` during development. Previously saved vault metadata is not updated by the app.
+### Upgrading an existing deployment to RBAC
+
+Merge #6 into #4 before merging #4 to `main`, and deploy the resulting code together.
+The base feature alone does not include the security fixes.
+
+Back up the database, configure the admin bootstrap, stop every old replica, and then
+start the new release with the normal migration-first command. This upgrade requires a
+maintenance window: migration `0005` removes the `state` column still used by the old
+server, so a mixed-version rolling deployment is incompatible. Rollback requires restoring
+the database backup as well as the old image. The migration lock prevents simultaneous
+migrators; it does not make old server code compatible with the new schema.
+
+The environment changes are:
+
+| Setting | RBAC behavior |
+| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `PN_ENTRA_DIRETTIVO_GROUP_ID` | New, optional group object ID for Direttivo. Unset grants nobody that evidence-backed role. |
+| `PN_ENTRA_OIDC_ADMIN_GROUP_ID` | Existing setting now grants Master Admin. Without it, PN accounts are not administrators. Requires complete PN tenant/client credentials when set. |
+| `IDP_ADMIN_USER_IDS` | Existing comma-separated local user IDs remain the explicit break-glass administrators. Configure at least this or the admin group before startup. |
+| `PN_ENTRA_MEMBER_REFRESH_HOURS` | Still controls persisted sign-in evidence, defaults to 24. It no longer determines authorization freshness. |
+
+Authorization uses a fixed one-minute Graph cache and five-second lookup deadline, with
+no new environment knobs. Configure the PN application with Graph `GroupMember.Read.All`
+application permission and tenant admin consent. A failed lookup grants no group access;
+the local break-glass IDs remain usable. Partial provider/mail credentials and malformed
+security settings now fail validation before migrations run.
+
## Connect identities
Sign in with Google or PoliNetwork Entra, then connect Telegram and a Polimi student email from the account page. Accounts are keyed by verified issuer and subject, with a database uniqueness constraint. Matching emails never merge users. Account links can have different email addresses. The last login method cannot be disconnected.
@@ -38,25 +65,133 @@ Register these callback URLs, replacing the origin with your deployment:
PoliNetwork Entra uses a tenant-specific registration, not the `common` tenant. Google and PoliNetwork Entra are login providers. Telegram uses the official OIDC authorization-code flow with PKCE and RS256 ID tokens, but the server only permits it through the account-linking flow. Configure its allowed origin and callback in BotFather. Its bot user ID comes from the signed `id` claim, separately from its OIDC `sub`.
-Polimi verification accepts only the exact `mail.polimi.it` domain. Codes contain six digits, expire after 10 minutes, allow five attempts, and cannot be resent for 60 seconds. The database stores only an HMAC of each code. Successful verification creates a `polimi-email` account link and grants student status for `STUDENT_VERIFICATION_TTL_DAYS`.
+Polimi verification accepts only the exact `mail.polimi.it` domain. Codes contain six digits, expire after 10 minutes, allow five attempts, and cannot be resent for 60 seconds. The cooldown applies to both the user and recipient and survives failed guesses, consumption, and failed delivery. The database stores only an HMAC of each code. Successful verification creates a `polimi-email` account link and grants student status for `STUDENT_VERIFICATION_TTL_DAYS`.
Email delivery uses the same Microsoft Graph client-credential setup as the current backend. The Azure application needs the Graph `Mail.Send` application permission and permission to send as `AZURE_EMAIL_SENDER`. These Azure credentials belong to the mail sender; they do not require access to Polimi Entra.
-## States and permissions
-
-| Evidence | State | Permission |
-| ----------------------------------------------- | ------------------ | ---------------------------------- |
-| PN Entra account in the configured `Soci` group | `socio` | `membership:read` |
-| Code sent to an `@mail.polimi.it` address | `student` | `student:verified` |
-| Telegram identity | Linked Telegram ID | No automatic moderation permission |
-
-States accumulate independently. A socio is not automatically a student. Signature, issuer, audience, expiration, and Entra tenant are checked before recording evidence. Evidence contributes only when joined to an account owned by the user.
-
-`PN_ENTRA_MEMBER_GROUP_ID` identifies the Microsoft Entra `Soci` group used by the backend. Membership is checked through Microsoft Graph using `PN_ENTRA_TENANT_ID`, `PN_ENTRA_CLIENT_ID`, and `PN_ENTRA_CLIENT_SECRET`. Grant Microsoft Graph **application** permission `GroupMember.Read.All` and admin consent on that PN app registration. `AZURE_*` credentials are only used for email delivery. The check reads direct group members across all result pages, matching the backend's membership rule. A Graph failure is logged and grants no new membership evidence; it is never cached as a confirmed nonmember or replaced by a token group claim.
-
-Microsoft membership is rechecked on login and on the first identity request after `PN_ENTRA_MEMBER_REFRESH_HOURS`, which defaults to 24 hours. This is a cache interval, not the duration of someone's membership. Expired evidence grants no state if Graph cannot verify it, and the next request retries. Polimi student verification lasts for `STUDENT_VERIFICATION_TTL_DAYS`, which defaults to 365 days. The user must verify the address again after that. The Telegram ownership link persists until disconnected. Already issued OIDC tokens expire after five minutes, so consumers must account for that revocation delay; `/api/identity` and UserInfo compute current evidence on each request.
-
-OIDC client administration is a separate permission from membership. Anyone signed in with a PoliNetwork Entra account (the `pn-entra` provider, verified against `PN_ENTRA_TENANT_ID`) can currently manage applications. To restrict it to a stricter Microsoft 365 group than Soci, set `PN_ENTRA_OIDC_ADMIN_GROUP_ID` to that group's object ID: only its direct members, checked through the same Graph credentials, keep access. Graph answers are cached for 15 minutes per user; a failed check denies access instead of caching. `IDP_ADMIN_USER_IDS` remains a break-glass allowlist of local user IDs that always pass. Being a socio never confers this permission by itself. Existing backend Telegram roles and group assignments remain authoritative and are not copied or queried by this prototype.
+## Roles and permissions
+
+Access is modelled as permissions bundled into roles. A **permission** is one thing an
+application can check for, addressed by a key such as `membership:read`. A **role** is a
+named bundle of permissions that someone can hold. Administrators create both at `/access`,
+and both support a hierarchy:
+
+- A permission can **also grant** other permissions. Holding `membership:write` can grant
+ `membership:read` without listing it everywhere.
+- A role can **inherit from** other roles. It then carries every permission of its parents,
+ including what those inherit in turn.
+
+Both hierarchies are transitive, and the editor refuses an edge that would make two roles
+inherit from each other or two permissions grant each other.
+
+### Roles the identity provider defines itself
+
+Four roles always exist and are never created, deleted, or handed out by an administrator.
+Their membership is conferred by the identity provider itself:
+
+| Role | Key | Granted by |
+| -------------- | -------------- | --------------------------------------------------------------------------------------------------- |
+| `Master Admin` | `master-admin` | `IDP_ADMIN_USER_IDS`; otherwise the configured administrators group, never an unconfigured fallback |
+| `Socio` | `socio` | Direct membership of the `Soci` group in PoliNetwork Entra ID |
+| `Direttivo` | `direttivo` | Direct membership of `PN_ENTRA_DIRETTIVO_GROUP_ID` in PoliNetwork Entra ID |
+| `Student` | `student` | A verification code delivered to an `@mail.polimi.it` address |
+
+**Master Admin holds every permission that exists**, including ones created after it was
+last looked at, because it is a wildcard rather than a stored list. It therefore has no
+grant list of its own to edit, and no role may inherit from it: that would launder a
+wildcard nobody can be given into a role an administrator could hand to anyone.
+Unlike the other three it is not proven by identity evidence and never appears among the
+`states`: it comes from the deployment's own configuration, which is what keeps the service
+from being locked out of its own administration. `IDP_ADMIN_USER_IDS` is always honored.
+Set `PN_ENTRA_OIDC_ADMIN_GROUP_ID` to limit everyone else to that Microsoft Entra group.
+If the group is unset, only the explicit allowlist can confer Master Admin. Startup fails
+without either an admin group plus complete PN Entra credentials or a nonempty allowlist.
+
+What the other three grant is still yours to choose: give them permissions, rename them,
+describe them, and place them in the hierarchy like any other role. Only their key, their
+deletion, and who holds them are fixed. The checked-in migrations seed them alongside the
+two permissions this service already issued, so existing consumers keep working: `socio`
+grants `membership:read` and `student` grants `student:verified`.
+
+`PN_ENTRA_DIRETTIVO_GROUP_ID` is optional and has no default. Until you set it to the
+board's Entra group object ID, nobody is inferred as Direttivo. Both group checks reuse the
+`PN_ENTRA_*` Graph credentials. Authorization rechecks membership with a fixed 60-second
+cache measured from lookup start. Stored sign-in evidence and `PN_ENTRA_MEMBER_REFRESH_HOURS`
+do not extend authorization. Failed or overlong checks grant nothing.
+
+Membership of the built-in roles is not a role assignment: nothing is written to
+`user_role` for them, and evidence contributes only when joined to an account owned by the
+user. States accumulate independently, so a socio is not automatically a student. Signature,
+issuer, audience, expiration, and Entra tenant are checked before evidence is recorded.
+
+Note that inheritance crosses this line in one direction. If you make a role you created
+inherit from `Socio`, everyone holding your role also reports the `socio` role and its
+permissions, whether or not Entra says they are a member. Inherit from a built-in role only
+when that is what you mean.
+
+### Permissions the identity provider defines itself
+
+Administering this service is expressed as permissions like any other capability, so it can
+be delegated to a role instead of being wired to a single group. These seven always exist
+and can never be created, deleted, or rekeyed, because the code checks for these exact
+keys; which roles carry them is entirely up to you.
+
+| Permission | Covers |
+| ------------------------ | ---------------------------------------------------------- |
+| `idp:people:read` | Searching the people registered here |
+| `idp:permissions:read` | Seeing permissions in the `/access` section |
+| `idp:permissions:write` | Creating, changing, and deleting permissions |
+| `idp:roles:read` | Seeing roles, what they grant, and who holds them |
+| `idp:roles:write` | Creating and changing roles, and giving them to people |
+| `idp:applications:read` | Seeing the OIDC applications at `/applications` |
+| `idp:applications:write` | Registering and editing applications, and rotating secrets |
+
+They use the permission hierarchy themselves: each `write` grants its `read`,
+`idp:roles:write` also grants `idp:people:read` so a role manager can find who to give a
+role to, and `idp:roles:read` grants `idp:permissions:read` because a role is meaningless
+without seeing the permissions it carries. A role with `idp:roles:write` therefore ends up
+with four permissions and still cannot touch applications. `idp:roles:write` granting
+`idp:roles:read` and `idp:permissions:write` granting `idp:permissions:read` are fixed in
+code: they apply even if the stored edge is missing, and cannot be removed, because
+changing either without seeing what already exists makes no sense. The other implications
+are seeded defaults you can edit.
+
+Every administration endpoint and every page checks the specific permission it needs, and
+the navigation only offers what you hold. Because Master Admin is a wildcard over every
+permission, whoever the deployment configures as an administrator holds all of these, which
+is the bootstrap and break-glass path: there is no second kind of check beside RBAC.
+
+Write permissions authorize bounded delegation. Only Master Admin can edit managed roles
+or permissions, including through custom ancestors or implications. Other writers can
+change, assign, revoke or delete only access within their current effective permissions;
+neither writer permission permits self-escalation. A new permission definition confers
+nothing: Master Admin must first grant it before others can delegate it. All checks use
+current authority inside the same serialized transaction as the mutation. Graph lookups
+finish before a database transaction starts. Inside the transaction, authorization rereads
+the actor's accounts, evidence, assigned roles and graph, using only still-valid cached
+membership answers. An account unlinked while Graph is pending cannot authorize the write.
+
+Every RBAC mutation records its actor, operation, target and before/after state in
+`rbac_audit_event`. These events commit atomically with the change and reject updates,
+deletes and truncation. Database owners remain trusted and can disable triggers; export
+audit events to separately controlled storage if protection from database owners is needed.
+
+### Assigning a role
+
+Roles you create are given to people from the role's page at `/access/roles`, which lists
+who holds it in pages of 100 and searches for someone to add. Searching requires
+`idp:people:read`; removing an existing member does not. An assignment lasts until it is removed.
+Deleting a role removes it from everyone who held it and from every role that inherited it.
+
+Changes take effect on the next token. Already-issued OIDC tokens expire after five
+minutes, so consumers must account for that revocation delay; `/api/identity` and UserInfo
+compute current access on each request. The role graph and assignments are read from one committed snapshot without a catalog
+cache. Requests starting after a database revocation commits see it. Group removal takes
+at most 60 seconds to affect new authorization decisions (subject to Graph propagation);
+a token issued just before expiry can remain valid for another five minutes.
+
+A linked Telegram identity grants no role and no permission. Existing backend Telegram
+roles and group assignments remain authoritative and are not copied or queried here.
## OIDC clients
@@ -68,16 +203,27 @@ Supported scopes are `openid`, `profile`, `polinetwork:identity`, and `offline_a
{
"https://auth.polinetwork.org/api/identity": {
"states": ["socio", "student"],
+ "roles": ["socio", "student"],
"permissions": ["membership:read", "student:verified"],
"telegramId": "123456789"
},
"polinetwork_states": "socio student",
+ "polinetwork_roles": "socio student",
"polinetwork_permissions": "membership:read student:verified",
"polinetwork_telegram_id": "123456789"
}
```
-The string `polinetwork_states` and `polinetwork_permissions` claims use spaces between values. They are empty strings when no values apply, as is `polinetwork_telegram_id` when no Telegram account is linked. The `/api/identity` response keeps the object format shown inside the URL-named claim.
+`states` is the raw evidence: what the person's linked accounts proved. `roles` and
+`permissions` are the result of resolving that evidence and their assignments through both
+hierarchies, so `roles` includes inherited parent roles and `permissions` includes
+everything granted indirectly. Applications should check `permissions` for a specific
+capability and treat `roles` as a coarser label. The string `polinetwork_*` claims use
+spaces between values and are empty strings when no values apply, as is
+`polinetwork_telegram_id` when no Telegram account is linked. The `/api/identity` response
+keeps the object format shown inside the URL-named claim.
+
+Managing applications needs the `idp:applications:write` permission, so it can be given to any role. Master Admin holds it only through explicit deployment configuration. To use a Microsoft 365 administrators group distinct from Soci, set `PN_ENTRA_OIDC_ADMIN_GROUP_ID` to that group's object ID: only its direct members, checked through the same Graph credentials, keep it. Graph answers are cached for at most 60 seconds from lookup start per user; a failed check denies access instead of caching. `IDP_ADMIN_USER_IDS` remains a break-glass allowlist of local user IDs that always pass. Being a socio never confers administration by itself.
Dynamic registration and client-credentials grants are disabled. Administrators manage clients at `/applications`: create web or native apps as confidential (secret shown once) or public (PKCE only) clients, edit redirect URIs and allowed scopes, rotate secrets, pause sign-ins by disabling an app, skip the consent screen for first-party apps, and delete apps. All administrators share one client pool (the plugin's `clientReference` is a fixed value), so clients are not tied to whoever created them. Redirect URIs follow the provider's rules: web apps need `https` on a public host, native apps may use `http://localhost`, `http://127.0.0.1`, `http://[::1]`, or a reverse-domain custom scheme. Custom routes under `/api/oidc/` back the pages; creation, deletion, and secret rotation go through the Better Auth client endpoints, which enforce the same administrator check.
@@ -109,8 +255,14 @@ IDENTITY_TEST_SECRET=your-test-server-secret \
vp test
```
-The integration suite covers discovery, anonymous rejection, current identity claims, denied client registration, unique account ownership, unlink revocation, and last-account protection. Live Google, Entra, Telegram, and Microsoft Graph email delivery require actual app registrations and have not been validated here.
+The integration suite covers discovery, anonymous rejection, current identity claims, denied client registration, unique account ownership, unlink revocation, and last-account protection. Role and permission resolution, hierarchy expansion, cycle refusal, and the protections around the built-in roles are covered by the unit tests in `src/auth/rbac.test.ts`. Live Google, Entra, Telegram, and Microsoft Graph email delivery require actual app registrations and have not been validated here.
The auth schema was generated with the Better Auth CLI and includes the `account.issuer` field and issuer/subject unique index required by installed Better Auth 1.7.2. Review regeneration diffs: older CLI core schemas omit that field. Generate Drizzle SQL with `vp run db:generate` after any schema change.
References: [Better Auth OAuth provider](https://better-auth.com/docs/plugins/oauth-provider), [Generic OAuth](https://better-auth.com/docs/plugins/generic-oauth), [Telegram OIDC](https://core.telegram.org/bots/telegram-login).
+
+The full RBAC security audit, findings, deployment changes and verification limits are in
+[docs/rbac-security-review.md](docs/rbac-security-review.md). The additional PostgreSQL suite
+runs when `RBAC_TEST_DATABASE_URL` points to a disposable migrated database. To run all tests
+without skips, provide that variable together with the HTTP integration variables above and
+the matching `DB_*`, `BETTER_AUTH_URL`, `BETTER_AUTH_SECRET` and admin bootstrap configuration.
diff --git a/docker/write-runtime-package.mjs b/docker/write-runtime-package.mjs
index 293de93..2dacd77 100644
--- a/docker/write-runtime-package.mjs
+++ b/docker/write-runtime-package.mjs
@@ -8,7 +8,7 @@
// the versions bundled into .output, which matters for Sentry in particular.
import { readFileSync, writeFileSync } from "node:fs";
-const runtimePackages = ["@sentry/tanstackstart-react", "drizzle-orm", "pg"];
+const runtimePackages = ["@sentry/tanstackstart-react", "drizzle-orm", "pg", "zod"];
const dependencies = Object.fromEntries(
runtimePackages.map((name) => {
diff --git a/docs/rbac-security-review.md b/docs/rbac-security-review.md
new file mode 100644
index 0000000..42afd54
--- /dev/null
+++ b/docs/rbac-security-review.md
@@ -0,0 +1,84 @@
+# RBAC security remediation for PR #6
+
+Audited base: `d3e466d9e56952dc423e6d3468557503d6ae918b`, the final stacked state of #6 on #4. Read the owner’s [security review](https://github.com/PoliNetworkOrg/auth/pull/6#issuecomment-5714698921), all issue comments and reviews on #4/#6, and the inline discussions. The owner approved replacing the documented root-equivalent writer behavior with bounded delegation during this remediation.
+
+## 1. Findings
+
+Locations below refer to the remediated source. “Review” identifies findings from the owner’s comment; the other rows come from the independent audit.
+
+| Issue | Severity | Location | Concrete exploit path | Status |
+| ------------------------------------------------------------------------- | --------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Review: fail-open Master Admin bootstrap | Critical | `src/auth/oidc-admin.ts:33`; `scripts/security-config.mjs:74` | A user with a formerly linked PN account signs in through Google/passkey while the admin group is unset and obtains every permission. | Fixed: explicit group/allowlist required; invalid deployment settings stop startup before migrations. |
+| Legacy inherited wildcard | Critical | `src/auth/rbac.ts:220`; `drizzle/0007_mushy_the_fury.sql:20` | A holder of a previously created custom role inheriting Master Admin obtains every present and future permission despite new-write validation. | Fixed: resolution ignores that edge; migration records/removes unsafe edges and assignments; database rejects new ones. |
+| Review: role-writer self-escalation and above-authority delegation | High | `src/auth/rbac-delegation.ts:36`; `src/auth/rbac-store.ts:235` | A role writer creates/edits a role with application administration, then assigns it to themselves or an accomplice. | Fixed: effective permissions before and after, including inheritance, must stay within the actor’s authority; managed changes require Master Admin. |
+| Review: permission-implication self-escalation | High | `src/auth/rbac-delegation.ts:24` | A permission writer makes a held permission transitively imply application administration, acquiring it on their next request. | Fixed: managed permissions require Master Admin; custom implication/rename changes cannot increase the actor’s authority. |
+| Indirect managed-role/permission changes | High | `src/auth/rbac-delegation.ts:44` | A delegated writer edits a custom ancestor or implied permission used by a managed role, changing managed grants without editing the managed row itself. | Fixed: compare affected effective role grants and managed permission closures across both graphs. |
+| Mutation/revocation race and unguarded store methods | High | `src/auth/rbac-store.ts:183`; `src/db/security-lock.ts:5` | A writer passes the HTTP guard, waits behind a revocation, then uses stale authority to grant another role; an internal caller could also omit the guard entirely. | Fixed: actor required by repository APIs; fresh authorization, dominance and writes share one READ COMMITTED transaction and advisory lock, including grant/revoke. |
+| Mixed-version graph resolution | High | `src/auth/rbac-store.ts:135`; `src/auth/rbac-store.ts:502` | A user races token issuance with a graph replacement and combines an old role grant with a new implication that never coexisted in a committed graph. | Fixed: graph, assignments and linked identity read from a REPEATABLE READ snapshot; no graph cache. |
+| Concurrent student-code guesses/replay | High | `src/auth/student-verification.ts:116` | An ordinary user submits concurrent guesses that overwrite the attempt counter, exceeds five guesses and can obtain Student and its configured permissions; concurrent valid requests can replay consumption. | Fixed: verification, attempts, code consumption and evidence creation share a serialized transaction; failed attempts commit before denial. |
+| Review: 24-hour group-backed authorization; separate 15-minute root cache | Medium | `src/auth/identity-subject.ts:11`; `src/auth/oidc-admin.ts:46` | A removed Socio/Direttivo/admin group member keeps using an existing session or minting fresh privileged tokens from stale membership. | Fixed: 60-second maximum application cache measured from lookup start; failed/overlong/superseded checks deny. |
+| Evidence from a former/different configured tenant | Medium | `src/auth/identity-subject.ts:18` | After a tenant change, an old linked account’s still-valid `pn-entra` evidence continues conferring membership without matching the new tenant. | Fixed: issuer and provider must match the configured trust boundary; current group verification is required. |
+| Restart restores revoked implications | Medium | `src/auth/rbac-store.ts:135` | A write-only actor reaches a cold replica after an operator removed a default read implication; runtime seeding recreates it and restores access. | Fixed: authorization reads never seed or repair the graph; regression exercises a fresh process. |
+| Review: no durable mutation audit / denial logging | Medium | `src/auth/rbac-store.ts:255`; `src/auth/denial-log.ts:2`; `drizzle/0007_mushy_the_fury.sql:12` | A compromised writer grants access, uses it and removes the grant, erasing attribution with the live assignment. | Fixed: actor, operation, target and before/after state recorded atomically; UPDATE/DELETE/TRUNCATE rejected; denials omit bodies, tokens and query strings. |
+| Write-only member disclosure | Medium | `src/routes/api/rbac/role-members.ts:36` | A writer whose read implication was removed mutates an empty role and receives all members’ names/emails in the response without role-read permission. | Fixed: separate read authorization, including inside the repository; write-only responses contain no member data. |
+| Unconfigured resource-policy SDK privileges | Medium, internal only | `src/auth/index.ts:118` | An internal caller passes an ordinary authenticated session to the provider’s resource-administration SDK and changes token policy because its absent privilege hook defaults to allow. | Fixed: unsupported resource-policy administration explicitly denies; real SDK denial regression. These endpoints were already server-only, not an anonymous/public HTTP exploit. |
+| Concurrent resend / last-account removal | Medium | `src/auth/student-verification.ts:53`; `src/auth/accounts.ts:18` | A user races resend requests to bypass the cooldown, or races two unlink requests so each sees the other login method and both are removed. | Fixed: read/check/write operations serialized; ownership remains derived from the authenticated subject. |
+
+No finding from the owner’s review was silently skipped or treated as a non-issue.
+
+## 2. Rebuttals and preserved decisions
+
+- **#4’s original cycle race was already fixed for graph saves at the reviewed commit.** `withRbacWriteLock` already took a transaction advisory lock and reread the graph. The missing piece was authorization and assignment/revocation under that same serialization. Concurrent opposite-edge regression tests verify exactly one edge commits.
+- **#4’s new-write Master Admin inheritance path was already blocked.** `validateRoleDraft` rejected Master Admin as a parent for every role, including managed roles. That restriction remains; only the resolver’s treatment of historical edges still needed fixing.
+- **The application pool is intentionally global, not user-owned tenancy.** `OIDC_CLIENT_REFERENCE` is the fixed `polinetwork` pool; custom SQL predicates and the installed provider’s client-reference checks reject a different pool. Real HTTP tests cover both ordinary/read-only rejection and cross-pool writer rejection. Per-user application ownership was not invented.
+- **Provider resource administration was not remotely exposed.** Installed provider endpoints carry `metadata: { SERVER_ONLY: true }`; an ordinary authenticated HTTP request returns 404. The separate internal SDK default was still explicitly closed.
+- **No prefix or wildcard permission matching exists.** Stored permission checks use exact keys. Unknown required keys and empty required sets deny. Master Admin expands only to catalog entries, and only direct configuration-derived membership activates it.
+- **Raw client claims are not proof.** PN/Telegram linking verifies signature, issuer, audience, expiry and tenant before recording evidence (`src/auth/providers.ts`). Authorization then joins evidence to an account owned by the persisted subject. Telegram provides no permission. The additional issuer check protects stored evidence after configuration changes.
+- **There is no RBAC “remove the last Master Admin” endpoint.** Managed roles cannot be assigned, unassigned or deleted; Master Admin membership lives in deployment configuration/Entra. Changing that external bootstrap remains an operator responsibility. Self-revocation of a delegated role remains allowed.
+- **Prior UI fixes remain intact.** The #4 discussion’s server-error display, synthetic preview key, busy controls, failed-load screens and permitted Access tab routing were checked in the full files. They were already addressed and were not reverted. #6’s independent catalog/read-only application boundaries remain in place.
+
+## 3. Deferred and limits
+
+No known code-remediable blocker in the audited RBAC paths is deliberately deferred. These boundaries remain:
+
+- Microsoft Graph propagation and real upstream provider availability are outside this repository. No live tenant, Google, Telegram or mail-delivery integration was exercised. Membership tests simulate positive, negative, expired, failed and superseded checks. A Graph outage loses group-derived access after the short cache expires; the explicit break-glass allowlist remains available.
+- The 60 seconds is the application cache bound for new authorization decisions, not a promise to cancel requests already in progress. Already-issued five-minute OIDC tokens remain valid until expiry. A token minted near the cache deadline can therefore retain group-derived rights for approximately six minutes, plus upstream propagation and consumer clock tolerance. Consumers requiring immediate revocation must use fresh authorization rather than offline token claims.
+- PostgreSQL owners and deployment operators are trusted. They can disable triggers/change configuration. Audit events resist application UPDATE/DELETE/TRUNCATE, but protection from database owners requires external, separately administered audit retention. Console denial logs likewise require an operational log sink.
+- A custom role may still inherit Socio/Direttivo/Student permissions without conferring the corresponding raw identity state. This is documented product behavior; downstream applications should authorize by permissions and use `states` when they need evidence of actual membership.
+- Bounded delegation protects the stored graph, not arbitrary downstream interpretations of new permission names or future edits made by Master Admin. Master Admin remains intentionally omnipotent.
+- Built-in OAuth client SDK actions check current authorization at their provider hook; an action already authorized before revocation may finish. Local RBAC mutations additionally reauthorize inside their serialized mutation transaction. There is no claim of distributed cancellation across the upstream identity provider and the database.
+
+## 4. Deployment and changed flows
+
+1. Configure **either** `PN_ENTRA_OIDC_ADMIN_GROUP_ID` with complete PN tenant/client credentials **or** a nonempty `IDP_ADMIN_USER_IDS` containing intended local user IDs. Missing/empty/malformed bootstrap settings now stop startup. Partial provider/mail credentials and malformed security settings also fail at startup.
+2. Apply migration `0007_mushy_the_fury.sql` before serving the new code. It creates append-only audit storage, rejects managed-role assignments/root inheritance, and records/removes existing unsafe links. The normal migration-first start command does this automatically. Runtime authorization no longer repairs missing seed rows: use the checked-in migrations.
+3. Role/permission writers are **no longer root-equivalent**, as explicitly approved. Ask Master Admin to edit managed access, grant new capabilities initially, rename a permission into an unheld capability, or change a graph affecting more privileged/managed roles. Existing delegates can continue delegating access they already hold.
+4. A linked PN account alone no longer grants administration. Former-tenant or malformed stored evidence grants nothing. Group-derived access now requires reachable Graph checks with a 60-second cache; `PN_ENTRA_MEMBER_REFRESH_HOURS` controls persisted sign-in evidence only.
+5. A write-only role membership mutation no longer returns member identities. Repository functions now require actor IDs, so internal callers must pass their authenticated actor rather than calling unguarded helpers.
+6. Unsupported resource-policy administration via the server SDK is denied. Standard shared-pool application management retains its existing permissions and UI.
+7. Concurrent verification attempts count individually; consumed codes cannot be replayed; concurrent resends and last-account removals are denied.
+
+Changed pre-existing authorization tests are deliberate: `oidc-admin.test.ts` now expects missing-group denial; `rbac.test.ts` now rejects a legacy inherited wildcard; `identity.integration.test.ts` no longer treats the fabricated issuer `pn-entra` as verified tenant evidence. Its Telegram fixture uses the canonical issuer, and valid student verification remains covered. No guard was weakened to satisfy those tests.
+
+## 5. Follow-up review of the complete stack
+
+The fresh review started at `87280c2`, compared the complete #4/#6 stack with `main`, and rechecked viganogabriele's report against the code. The four original security controls remain in place. Additional findings and fixes:
+
+- **Graph I/O blocked unrelated security writes.** Membership refresh now happens before opening a transaction. Transactional authorization rereads current owned accounts/evidence and checks only unexpired cached group facts. It cannot authorize an account unlinked during the lookup. Lookups have a five-second deadline and share in-flight work per group/person. Concurrent valid administrators no longer supersede and deny one another's checks.
+- **Successful saves could return another transaction's state or fail after committing.** Role and permission saves now return their own transactional summary.
+- **Self-revocation could return a misleading 403 after success.** Membership writes return an acknowledgement without member data. The UI refreshes access separately.
+- **Unbounded member responses replaced the old silent cutoff.** The endpoint and UI now use cursor pagination with 100 members per page. A 505-person regression verifies complete traversal without duplicates.
+- **Confirmation could reset email resend throttling.** Consumed, exhausted and failed-delivery challenges retain their send timestamps; mismatched emails do not delete the original challenge. Five database regressions cover throttling, replay and delayed delivery failures.
+- **Delegated writers saw controls the server would always reject.** The UI now distinguishes Master Admin, makes built-in and above-authority objects read-only, limits grant choices, and respects the separate people-search permission. The server still validates the complete proposed graph.
+- **Rollout documentation omitted a schema compatibility break.** The README now requires stopping old replicas before migration `0005` removes their `state` column. Merge #6 into #4 first and deploy the combined release. Rollback requires the database backup and old image.
+
+No additional environment variables or migrations are needed for these follow-up fixes. The stack still requires explicit admin bootstrap and migrations `0004` through `0007`. The [Microsoft Graph SDK cancellation guidance](https://github.com/microsoftgraph/msgraph-sdk-javascript/wiki/Microsoft-Graph-JavaScript-SDK-V3.0-Upgrade-Guide) documents the request signal used for the lookup deadline.
+
+Validation: formatting, lint, TypeScript, production build, Docker image build/startup, and the full suite against disposable PostgreSQL and the compiled HTTP server: **152 passed, zero skipped**. Upgrade rehearsals from `main` and the original RBAC branch preserve existing states and record/remove unsafe legacy links. Missing bootstrap configuration stops the container before migrations. Browser checks cover delegated read-only controls, Master Admin editing and page navigation. The integration tests include a blocked Graph lookup concurrent with an unrelated write and account unlink, proving both progress and denial of stale authority. The limits in section 3 still apply.
+
+## Authorization model and coverage notes
+
+- Resolution order: persisted user → owned accounts and trusted evidence → current group checks/student evidence → configured Master Admin + manual custom roles → role inheritance → transitive permission implications. There are no explicit deny rules or client-selected tenants. Roles/permissions use normalized lowercase keys; reads/checks use exact matching. Only Master Admin has wildcard behavior.
+- HTTP entry points audited: `/api/identity`, `/api/idp/access`, every `/api/rbac/*` and `/api/oidc/*` route, account unlink, student verification, providers, and the `/api/auth/$` dispatcher. Also inspected OAuth client hooks, internal/admin SDK methods, token/UserInfo claim callbacks, provider verification and direct repository callers. No bulk or webhook RBAC mutation endpoint exists.
+- Database enforcement: PKs/unique keys, issuer-subject ownership uniqueness, edge/assignment FKs and delete cascades; new triggers forbid managed assignments, Master Admin parents and audit rewrites. Cycle/dominance rules remain application-level but execute under the shared advisory lock. Account unlink and student evidence mutation use that lock too.
+- Test infrastructure: disposable PostgreSQL 17 on loopback, no production data; real signed-cookie tests run against the compiled server. The additional repository/route integration suite substitutes session authentication and Graph/mail responses but uses real authorization code, SQL, transactions and constraints. Group cache tests use controlled time. Startup configuration denial was also checked before migrations.
diff --git a/drizzle/0004_overjoyed_mordo.sql b/drizzle/0004_overjoyed_mordo.sql
new file mode 100644
index 0000000..46f18c1
--- /dev/null
+++ b/drizzle/0004_overjoyed_mordo.sql
@@ -0,0 +1,80 @@
+CREATE TABLE "permission" (
+ "id" text PRIMARY KEY NOT NULL,
+ "key" text NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "createdAt" timestamp with time zone DEFAULT now() NOT NULL,
+ "updatedAt" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "permission_implication" (
+ "permission_id" text NOT NULL,
+ "implied_permission_id" text NOT NULL,
+ CONSTRAINT "permission_implication_permission_id_implied_permission_id_pk" PRIMARY KEY("permission_id","implied_permission_id")
+);
+--> statement-breakpoint
+CREATE TABLE "role" (
+ "id" text PRIMARY KEY NOT NULL,
+ "key" text NOT NULL,
+ "name" text NOT NULL,
+ "description" text,
+ "managed" boolean DEFAULT false NOT NULL,
+ "source_state" text,
+ "createdAt" timestamp with time zone DEFAULT now() NOT NULL,
+ "updatedAt" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE TABLE "role_parent" (
+ "role_id" text NOT NULL,
+ "parent_role_id" text NOT NULL,
+ CONSTRAINT "role_parent_role_id_parent_role_id_pk" PRIMARY KEY("role_id","parent_role_id")
+);
+--> statement-breakpoint
+CREATE TABLE "role_permission" (
+ "role_id" text NOT NULL,
+ "permission_id" text NOT NULL,
+ CONSTRAINT "role_permission_role_id_permission_id_pk" PRIMARY KEY("role_id","permission_id")
+);
+--> statement-breakpoint
+CREATE TABLE "user_role" (
+ "user_id" text NOT NULL,
+ "role_id" text NOT NULL,
+ "assigned_by" text,
+ "assignedAt" timestamp with time zone DEFAULT now() NOT NULL,
+ CONSTRAINT "user_role_user_id_role_id_pk" PRIMARY KEY("user_id","role_id")
+);
+--> statement-breakpoint
+ALTER TABLE "identity_evidence" ADD COLUMN "states" text[] DEFAULT '{}' NOT NULL;--> statement-breakpoint
+ALTER TABLE "permission_implication" ADD CONSTRAINT "permission_implication_permission_id_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "public"."permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "permission_implication" ADD CONSTRAINT "permission_implication_implied_permission_id_permission_id_fk" FOREIGN KEY ("implied_permission_id") REFERENCES "public"."permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "role_parent" ADD CONSTRAINT "role_parent_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "role_parent" ADD CONSTRAINT "role_parent_parent_role_id_role_id_fk" FOREIGN KEY ("parent_role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "role_permission" ADD CONSTRAINT "role_permission_permission_id_permission_id_fk" FOREIGN KEY ("permission_id") REFERENCES "public"."permission"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_role" ADD CONSTRAINT "user_role_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+ALTER TABLE "user_role" ADD CONSTRAINT "user_role_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
+CREATE UNIQUE INDEX "permission_key_uidx" ON "permission" USING btree ("key");--> statement-breakpoint
+CREATE INDEX "permissionImplication_implied_idx" ON "permission_implication" USING btree ("implied_permission_id");--> statement-breakpoint
+CREATE UNIQUE INDEX "role_key_uidx" ON "role" USING btree ("key");--> statement-breakpoint
+CREATE INDEX "roleParent_parent_idx" ON "role_parent" USING btree ("parent_role_id");--> statement-breakpoint
+CREATE INDEX "rolePermission_permission_idx" ON "role_permission" USING btree ("permission_id");--> statement-breakpoint
+CREATE INDEX "userRole_role_idx" ON "user_role" USING btree ("role_id");--> statement-breakpoint
+-- Carry the single state each account proved into the new list before 0005 drops it.
+UPDATE "identity_evidence" SET "states" = ARRAY["state"] WHERE "state" IS NOT NULL;--> statement-breakpoint
+-- The roles the identity provider defines itself. Membership follows identity evidence:
+-- the application never writes user_role rows for these. Names, descriptions, permissions,
+-- and hierarchy are administrator-editable from here on, so this seed never runs again.
+INSERT INTO "role" ("id", "key", "name", "description", "managed", "source_state") VALUES
+ ('static-role-socio', 'socio', 'Socio', 'Member of PoliNetwork APS.', true, 'socio'),
+ ('static-role-direttivo', 'direttivo', 'Direttivo', 'Member of the PoliNetwork APS board.', true, 'direttivo'),
+ ('static-role-student', 'student', 'Student', 'Verified Politecnico di Milano student.', true, 'student')
+ON CONFLICT ("key") DO NOTHING;--> statement-breakpoint
+-- The two permissions this service already put in tokens, so existing consumers keep working.
+INSERT INTO "permission" ("id", "key", "name", "description") VALUES
+ ('seed-permission-membership-read', 'membership:read', 'Read membership', 'See that someone is a member of PoliNetwork APS.'),
+ ('seed-permission-student-verified', 'student:verified', 'Verified student', 'See that someone verified a Politecnico di Milano student email.')
+ON CONFLICT ("key") DO NOTHING;--> statement-breakpoint
+INSERT INTO "role_permission" ("role_id", "permission_id") VALUES
+ ('static-role-socio', 'seed-permission-membership-read'),
+ ('static-role-student', 'seed-permission-student-verified')
+ON CONFLICT DO NOTHING;
diff --git a/drizzle/0005_left_lizard.sql b/drizzle/0005_left_lizard.sql
new file mode 100644
index 0000000..27ebc64
--- /dev/null
+++ b/drizzle/0005_left_lizard.sql
@@ -0,0 +1 @@
+ALTER TABLE "identity_evidence" DROP COLUMN "state";
\ No newline at end of file
diff --git a/drizzle/0006_volatile_pandemic.sql b/drizzle/0006_volatile_pandemic.sql
new file mode 100644
index 0000000..ff7d86c
--- /dev/null
+++ b/drizzle/0006_volatile_pandemic.sql
@@ -0,0 +1,31 @@
+ALTER TABLE "permission" ADD COLUMN "managed" boolean DEFAULT false NOT NULL;--> statement-breakpoint
+-- Master Admin holds every permission that exists, as a wildcard rather than a stored
+-- grant list, so it keeps covering permissions created later. Its membership comes from
+-- IDP_ADMIN_USER_IDS or the Entra administration policy rather than from identity evidence.
+-- Master Admin requires an explicitly configured administrators group or user allowlist.
+-- It has no source_state because deployment configuration provides the bootstrap path
+-- independently of the editable role graph.
+INSERT INTO "role" ("id", "key", "name", "description", "managed", "source_state") VALUES
+ ('static-role-master-admin', 'master-admin', 'Master Admin', 'Complete control of this identity provider.', true, NULL)
+ON CONFLICT ("key") DO NOTHING;--> statement-breakpoint
+-- The permissions covering this identity provider's own administration. The code checks
+-- for these exact keys, so they can never be created or deleted by hand; which roles carry
+-- them is entirely up to the administrator.
+INSERT INTO "permission" ("id", "key", "name", "description", "managed") VALUES
+ ('managed-permission-idp-people-read', 'idp:people:read', 'Find people', 'Search the people registered with this identity provider.', true),
+ ('managed-permission-idp-permissions-read', 'idp:permissions:read', 'View permissions', 'See the permissions this identity provider defines.', true),
+ ('managed-permission-idp-permissions-write', 'idp:permissions:write', 'Manage permissions', 'Create, change, and delete permissions, and choose what each one also grants.', true),
+ ('managed-permission-idp-roles-read', 'idp:roles:read', 'View roles', 'See roles, what they grant, and who holds them.', true),
+ ('managed-permission-idp-roles-write', 'idp:roles:write', 'Manage roles', 'Create, change, and delete roles, and give them to people.', true),
+ ('managed-permission-idp-applications-read', 'idp:applications:read', 'View applications', 'See the applications that sign people in with PoliNetwork Identity.', true),
+ ('managed-permission-idp-applications-write', 'idp:applications:write', 'Manage applications', 'Register applications, edit their redirect URIs and scopes, rotate secrets, and delete them.', true)
+ON CONFLICT ("key") DO NOTHING;--> statement-breakpoint
+-- Writing implies reading, managing roles implies finding the people to give them to, and
+-- understanding a role means being able to see the permissions it carries.
+INSERT INTO "permission_implication" ("permission_id", "implied_permission_id") VALUES
+ ('managed-permission-idp-permissions-write', 'managed-permission-idp-permissions-read'),
+ ('managed-permission-idp-roles-read', 'managed-permission-idp-permissions-read'),
+ ('managed-permission-idp-roles-write', 'managed-permission-idp-roles-read'),
+ ('managed-permission-idp-roles-write', 'managed-permission-idp-people-read'),
+ ('managed-permission-idp-applications-write', 'managed-permission-idp-applications-read')
+ON CONFLICT DO NOTHING;
diff --git a/drizzle/0007_mushy_the_fury.sql b/drizzle/0007_mushy_the_fury.sql
new file mode 100644
index 0000000..f271cc0
--- /dev/null
+++ b/drizzle/0007_mushy_the_fury.sql
@@ -0,0 +1,51 @@
+CREATE TABLE "rbac_audit_event" (
+ "id" text PRIMARY KEY NOT NULL,
+ "actor_id" text NOT NULL,
+ "operation" text NOT NULL,
+ "target_id" text NOT NULL,
+ "before" jsonb NOT NULL,
+ "after" jsonb NOT NULL,
+ "createdAt" timestamp with time zone DEFAULT now() NOT NULL
+);
+--> statement-breakpoint
+CREATE FUNCTION reject_rbac_audit_mutation() RETURNS trigger LANGUAGE plpgsql AS $$
+BEGIN
+ RAISE EXCEPTION 'RBAC audit events are append-only';
+END;
+$$;
+--> statement-breakpoint
+CREATE TRIGGER rbac_audit_append_only BEFORE UPDATE OR DELETE OR TRUNCATE ON rbac_audit_event
+FOR EACH STATEMENT EXECUTE FUNCTION reject_rbac_audit_mutation();
+--> statement-breakpoint
+CREATE FUNCTION guard_managed_role_links() RETURNS trigger LANGUAGE plpgsql AS $$
+BEGIN
+ IF TG_TABLE_NAME = 'user_role' THEN
+ IF EXISTS (SELECT 1 FROM role WHERE id = NEW.role_id AND managed) THEN
+ RAISE EXCEPTION 'Managed roles cannot be assigned';
+ END IF;
+ ELSE
+ IF EXISTS (SELECT 1 FROM role WHERE id = NEW.parent_role_id AND key = 'master-admin') THEN
+ RAISE EXCEPTION 'Master Admin cannot be inherited';
+ END IF;
+ END IF;
+ RETURN NEW;
+END;
+$$;
+--> statement-breakpoint
+CREATE TRIGGER user_role_unmanaged_only BEFORE INSERT OR UPDATE ON user_role
+FOR EACH ROW EXECUTE FUNCTION guard_managed_role_links();
+--> statement-breakpoint
+CREATE TRIGGER role_parent_no_master BEFORE INSERT OR UPDATE ON role_parent
+FOR EACH ROW EXECUTE FUNCTION guard_managed_role_links();
+--> statement-breakpoint
+-- Existing unsafe edges/assignments are quarantined in the audit record before removal.
+INSERT INTO rbac_audit_event (id, actor_id, operation, target_id, before, after)
+SELECT 'migration-0007-unsafe-links', 'system:migration:0007', 'quarantine', 'managed-role-links',
+jsonb_build_object(
+ 'parents', (SELECT coalesce(jsonb_agg(p), '[]') FROM role_parent p JOIN role r ON r.id = p.parent_role_id WHERE r.key = 'master-admin'),
+ 'assignments', (SELECT coalesce(jsonb_agg(a), '[]') FROM user_role a JOIN role r ON r.id = a.role_id WHERE r.managed)
+), '{}'::jsonb;
+--> statement-breakpoint
+DELETE FROM role_parent WHERE parent_role_id IN (SELECT id FROM role WHERE key = 'master-admin');
+--> statement-breakpoint
+DELETE FROM user_role WHERE role_id IN (SELECT id FROM role WHERE managed);
diff --git a/drizzle/meta/0004_snapshot.json b/drizzle/meta/0004_snapshot.json
new file mode 100644
index 0000000..e60b76e
--- /dev/null
+++ b/drizzle/meta/0004_snapshot.json
@@ -0,0 +1,2246 @@
+{
+ "id": "a98f0cd1-6a05-43c5-b237-221ada57650b",
+ "prevId": "ad684e52-2757-47da-9c8b-5a401e669d8e",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_issuer_subject_uidx": {
+ "name": "account_issuer_subject_uidx",
+ "columns": [
+ {
+ "expression": "issuer",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.jwks": {
+ "name": "jwks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alg": {
+ "name": "alg",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "crv": {
+ "name": "crv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_access_token": {
+ "name": "oauth_access_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_id": {
+ "name": "refresh_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthAccessToken_clientId_idx": {
+ "name": "oauthAccessToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_sessionId_idx": {
+ "name": "oauthAccessToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_userId_idx": {
+ "name": "oauthAccessToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_authorizationCodeId_idx": {
+ "name": "oauthAccessToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_refreshId_idx": {
+ "name": "oauthAccessToken_refreshId_idx",
+ "columns": [
+ {
+ "expression": "refresh_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_access_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_access_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_session_id_session_id_fk": {
+ "name": "oauth_access_token_session_id_session_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_user_id_user_id_fk": {
+ "name": "oauth_access_token_user_id_user_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": {
+ "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_refresh_token",
+ "columnsFrom": ["refresh_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_access_token_token_unique": {
+ "name": "oauth_access_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client": {
+ "name": "oauth_client",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_discovery_id": {
+ "name": "client_discovery_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "skip_consent": {
+ "name": "skip_consent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_end_session": {
+ "name": "enable_end_session",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subject_type": {
+ "name": "subject_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_credentials_scopes": {
+ "name": "client_credentials_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uri": {
+ "name": "uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contacts": {
+ "name": "contacts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tos": {
+ "name": "tos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_id": {
+ "name": "software_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_version": {
+ "name": "software_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_statement": {
+ "name": "software_statement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "post_logout_redirect_uris": {
+ "name": "post_logout_redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_uri": {
+ "name": "backchannel_logout_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_session_required": {
+ "name": "backchannel_logout_session_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_endpoint_auth_method": {
+ "name": "token_endpoint_auth_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_type": {
+ "name": "application_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks": {
+ "name": "jwks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks_uri": {
+ "name": "jwks_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "grant_types": {
+ "name": "grant_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response_types": {
+ "name": "response_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "require_pkce": {
+ "name": "require_pkce",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens": {
+ "name": "dpop_bound_access_tokens",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClient_userId_idx": {
+ "name": "oauthClient_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_user_id_user_id_fk": {
+ "name": "oauth_client_user_id_user_id_fk",
+ "tableFrom": "oauth_client",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_client_client_id_unique": {
+ "name": "oauth_client_client_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["client_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_assertion": {
+ "name": "oauth_client_assertion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_resource": {
+ "name": "oauth_client_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClientResource_clientId_resourceId_uidx": {
+ "name": "oauthClientResource_clientId_resourceId_uidx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_clientId_idx": {
+ "name": "oauthClientResource_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_resourceId_idx": {
+ "name": "oauthClientResource_resourceId_idx",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_resource_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_client_resource_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_client_resource_resource_id_oauth_resource_identifier_fk": {
+ "name": "oauth_client_resource_resource_id_oauth_resource_identifier_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["identifier"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_consent": {
+ "name": "oauth_consent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthConsent_clientId_idx": {
+ "name": "oauthConsent_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthConsent_userId_idx": {
+ "name": "oauthConsent_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_consent_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_consent_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_consent_user_id_user_id_fk": {
+ "name": "oauth_consent_user_id_user_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_refresh_token": {
+ "name": "oauth_refresh_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotated_at": {
+ "name": "rotated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_response": {
+ "name": "rotation_replay_response",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_expires_at": {
+ "name": "rotation_replay_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_time": {
+ "name": "auth_time",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthRefreshToken_clientId_idx": {
+ "name": "oauthRefreshToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_sessionId_idx": {
+ "name": "oauthRefreshToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_userId_idx": {
+ "name": "oauthRefreshToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_authorizationCodeId_idx": {
+ "name": "oauthRefreshToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_refresh_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_session_id_session_id_fk": {
+ "name": "oauth_refresh_token_session_id_session_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_user_id_user_id_fk": {
+ "name": "oauth_refresh_token_user_id_user_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_refresh_token_token_unique": {
+ "name": "oauth_refresh_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_resource": {
+ "name": "oauth_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token_ttl": {
+ "name": "access_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_ttl": {
+ "name": "refresh_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_algorithm": {
+ "name": "signing_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_key_id": {
+ "name": "signing_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_scopes": {
+ "name": "allowed_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_claims": {
+ "name": "custom_claims",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens_required": {
+ "name": "dpop_bound_access_tokens_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy_version": {
+ "name": "policy_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_resource_identifier_unique": {
+ "name": "oauth_resource_identifier_unique",
+ "nullsNotDistinct": false,
+ "columns": ["identifier"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.passkey": {
+ "name": "passkey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "aaguid": {
+ "name": "aaguid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "passkey_userId_idx": {
+ "name": "passkey_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "passkey_user_id_user_id_fk": {
+ "name": "passkey_user_id_user_id_fk",
+ "tableFrom": "passkey",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "passkey_credential_id_unique": {
+ "name": "passkey_credential_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["credential_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit": {
+ "name": "rate_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "rate_limit_key_unique": {
+ "name": "rate_limit_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_evidence": {
+ "name": "identity_evidence",
+ "schema": "",
+ "columns": {
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "state": {
+ "name": "state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "states": {
+ "name": "states",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "valid_until": {
+ "name": "valid_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "telegram_id": {
+ "name": "telegram_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "identity_evidence_issuer_subject_pk": {
+ "name": "identity_evidence_issuer_subject_pk",
+ "columns": ["issuer", "subject"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.student_verification_challenge": {
+ "name": "student_verification_challenge",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_sent_at": {
+ "name": "last_sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "studentVerificationChallenge_email_uidx": {
+ "name": "studentVerificationChallenge_email_uidx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "studentVerificationChallenge_expiresAt_idx": {
+ "name": "studentVerificationChallenge_expiresAt_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "student_verification_challenge_user_id_user_id_fk": {
+ "name": "student_verification_challenge_user_id_user_id_fk",
+ "tableFrom": "student_verification_challenge",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission": {
+ "name": "permission",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_key_uidx": {
+ "name": "permission_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_implication": {
+ "name": "permission_implication",
+ "schema": "",
+ "columns": {
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "implied_permission_id": {
+ "name": "implied_permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "permissionImplication_implied_idx": {
+ "name": "permissionImplication_implied_idx",
+ "columns": [
+ {
+ "expression": "implied_permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_implication_permission_id_permission_id_fk": {
+ "name": "permission_implication_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_implication_implied_permission_id_permission_id_fk": {
+ "name": "permission_implication_implied_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["implied_permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "permission_implication_permission_id_implied_permission_id_pk": {
+ "name": "permission_implication_permission_id_implied_permission_id_pk",
+ "columns": ["permission_id", "implied_permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "source_state": {
+ "name": "source_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "role_key_uidx": {
+ "name": "role_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_parent": {
+ "name": "role_parent",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_role_id": {
+ "name": "parent_role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "roleParent_parent_idx": {
+ "name": "roleParent_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_parent_role_id_role_id_fk": {
+ "name": "role_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_parent_parent_role_id_role_id_fk": {
+ "name": "role_parent_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["parent_role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_parent_role_id_parent_role_id_pk": {
+ "name": "role_parent_role_id_parent_role_id_pk",
+ "columns": ["role_id", "parent_role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_permission": {
+ "name": "role_permission",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "rolePermission_permission_idx": {
+ "name": "rolePermission_permission_idx",
+ "columns": [
+ {
+ "expression": "permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_permission_role_id_role_id_fk": {
+ "name": "role_permission_role_id_role_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_permission_permission_id_permission_id_fk": {
+ "name": "role_permission_permission_id_permission_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_permission_role_id_permission_id_pk": {
+ "name": "role_permission_role_id_permission_id_pk",
+ "columns": ["role_id", "permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assignedAt": {
+ "name": "assignedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "userRole_role_idx": {
+ "name": "userRole_role_idx",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_role_user_id_user_id_fk": {
+ "name": "user_role_user_id_user_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_role_role_id_role_id_fk": {
+ "name": "user_role_role_id_role_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_role_user_id_role_id_pk": {
+ "name": "user_role_user_id_role_id_pk",
+ "columns": ["user_id", "role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/0005_snapshot.json b/drizzle/meta/0005_snapshot.json
new file mode 100644
index 0000000..061ec68
--- /dev/null
+++ b/drizzle/meta/0005_snapshot.json
@@ -0,0 +1,2240 @@
+{
+ "id": "38e3db88-4f08-43e5-96b0-0c9d8264b0a5",
+ "prevId": "a98f0cd1-6a05-43c5-b237-221ada57650b",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_issuer_subject_uidx": {
+ "name": "account_issuer_subject_uidx",
+ "columns": [
+ {
+ "expression": "issuer",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.jwks": {
+ "name": "jwks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alg": {
+ "name": "alg",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "crv": {
+ "name": "crv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_access_token": {
+ "name": "oauth_access_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_id": {
+ "name": "refresh_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthAccessToken_clientId_idx": {
+ "name": "oauthAccessToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_sessionId_idx": {
+ "name": "oauthAccessToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_userId_idx": {
+ "name": "oauthAccessToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_authorizationCodeId_idx": {
+ "name": "oauthAccessToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_refreshId_idx": {
+ "name": "oauthAccessToken_refreshId_idx",
+ "columns": [
+ {
+ "expression": "refresh_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_access_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_access_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_session_id_session_id_fk": {
+ "name": "oauth_access_token_session_id_session_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_user_id_user_id_fk": {
+ "name": "oauth_access_token_user_id_user_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": {
+ "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_refresh_token",
+ "columnsFrom": ["refresh_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_access_token_token_unique": {
+ "name": "oauth_access_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client": {
+ "name": "oauth_client",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_discovery_id": {
+ "name": "client_discovery_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "skip_consent": {
+ "name": "skip_consent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_end_session": {
+ "name": "enable_end_session",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subject_type": {
+ "name": "subject_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_credentials_scopes": {
+ "name": "client_credentials_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uri": {
+ "name": "uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contacts": {
+ "name": "contacts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tos": {
+ "name": "tos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_id": {
+ "name": "software_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_version": {
+ "name": "software_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_statement": {
+ "name": "software_statement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "post_logout_redirect_uris": {
+ "name": "post_logout_redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_uri": {
+ "name": "backchannel_logout_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_session_required": {
+ "name": "backchannel_logout_session_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_endpoint_auth_method": {
+ "name": "token_endpoint_auth_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_type": {
+ "name": "application_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks": {
+ "name": "jwks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks_uri": {
+ "name": "jwks_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "grant_types": {
+ "name": "grant_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response_types": {
+ "name": "response_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "require_pkce": {
+ "name": "require_pkce",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens": {
+ "name": "dpop_bound_access_tokens",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClient_userId_idx": {
+ "name": "oauthClient_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_user_id_user_id_fk": {
+ "name": "oauth_client_user_id_user_id_fk",
+ "tableFrom": "oauth_client",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_client_client_id_unique": {
+ "name": "oauth_client_client_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["client_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_assertion": {
+ "name": "oauth_client_assertion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_resource": {
+ "name": "oauth_client_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClientResource_clientId_resourceId_uidx": {
+ "name": "oauthClientResource_clientId_resourceId_uidx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_clientId_idx": {
+ "name": "oauthClientResource_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_resourceId_idx": {
+ "name": "oauthClientResource_resourceId_idx",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_resource_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_client_resource_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_client_resource_resource_id_oauth_resource_identifier_fk": {
+ "name": "oauth_client_resource_resource_id_oauth_resource_identifier_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["identifier"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_consent": {
+ "name": "oauth_consent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthConsent_clientId_idx": {
+ "name": "oauthConsent_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthConsent_userId_idx": {
+ "name": "oauthConsent_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_consent_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_consent_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_consent_user_id_user_id_fk": {
+ "name": "oauth_consent_user_id_user_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_refresh_token": {
+ "name": "oauth_refresh_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotated_at": {
+ "name": "rotated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_response": {
+ "name": "rotation_replay_response",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_expires_at": {
+ "name": "rotation_replay_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_time": {
+ "name": "auth_time",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthRefreshToken_clientId_idx": {
+ "name": "oauthRefreshToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_sessionId_idx": {
+ "name": "oauthRefreshToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_userId_idx": {
+ "name": "oauthRefreshToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_authorizationCodeId_idx": {
+ "name": "oauthRefreshToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_refresh_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_session_id_session_id_fk": {
+ "name": "oauth_refresh_token_session_id_session_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_user_id_user_id_fk": {
+ "name": "oauth_refresh_token_user_id_user_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_refresh_token_token_unique": {
+ "name": "oauth_refresh_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_resource": {
+ "name": "oauth_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token_ttl": {
+ "name": "access_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_ttl": {
+ "name": "refresh_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_algorithm": {
+ "name": "signing_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_key_id": {
+ "name": "signing_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_scopes": {
+ "name": "allowed_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_claims": {
+ "name": "custom_claims",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens_required": {
+ "name": "dpop_bound_access_tokens_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy_version": {
+ "name": "policy_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_resource_identifier_unique": {
+ "name": "oauth_resource_identifier_unique",
+ "nullsNotDistinct": false,
+ "columns": ["identifier"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.passkey": {
+ "name": "passkey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "aaguid": {
+ "name": "aaguid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "passkey_userId_idx": {
+ "name": "passkey_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "passkey_user_id_user_id_fk": {
+ "name": "passkey_user_id_user_id_fk",
+ "tableFrom": "passkey",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "passkey_credential_id_unique": {
+ "name": "passkey_credential_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["credential_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit": {
+ "name": "rate_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "rate_limit_key_unique": {
+ "name": "rate_limit_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_evidence": {
+ "name": "identity_evidence",
+ "schema": "",
+ "columns": {
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "states": {
+ "name": "states",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "valid_until": {
+ "name": "valid_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "telegram_id": {
+ "name": "telegram_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "identity_evidence_issuer_subject_pk": {
+ "name": "identity_evidence_issuer_subject_pk",
+ "columns": ["issuer", "subject"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.student_verification_challenge": {
+ "name": "student_verification_challenge",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_sent_at": {
+ "name": "last_sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "studentVerificationChallenge_email_uidx": {
+ "name": "studentVerificationChallenge_email_uidx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "studentVerificationChallenge_expiresAt_idx": {
+ "name": "studentVerificationChallenge_expiresAt_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "student_verification_challenge_user_id_user_id_fk": {
+ "name": "student_verification_challenge_user_id_user_id_fk",
+ "tableFrom": "student_verification_challenge",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission": {
+ "name": "permission",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_key_uidx": {
+ "name": "permission_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_implication": {
+ "name": "permission_implication",
+ "schema": "",
+ "columns": {
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "implied_permission_id": {
+ "name": "implied_permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "permissionImplication_implied_idx": {
+ "name": "permissionImplication_implied_idx",
+ "columns": [
+ {
+ "expression": "implied_permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_implication_permission_id_permission_id_fk": {
+ "name": "permission_implication_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_implication_implied_permission_id_permission_id_fk": {
+ "name": "permission_implication_implied_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["implied_permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "permission_implication_permission_id_implied_permission_id_pk": {
+ "name": "permission_implication_permission_id_implied_permission_id_pk",
+ "columns": ["permission_id", "implied_permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "source_state": {
+ "name": "source_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "role_key_uidx": {
+ "name": "role_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_parent": {
+ "name": "role_parent",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_role_id": {
+ "name": "parent_role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "roleParent_parent_idx": {
+ "name": "roleParent_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_parent_role_id_role_id_fk": {
+ "name": "role_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_parent_parent_role_id_role_id_fk": {
+ "name": "role_parent_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["parent_role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_parent_role_id_parent_role_id_pk": {
+ "name": "role_parent_role_id_parent_role_id_pk",
+ "columns": ["role_id", "parent_role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_permission": {
+ "name": "role_permission",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "rolePermission_permission_idx": {
+ "name": "rolePermission_permission_idx",
+ "columns": [
+ {
+ "expression": "permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_permission_role_id_role_id_fk": {
+ "name": "role_permission_role_id_role_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_permission_permission_id_permission_id_fk": {
+ "name": "role_permission_permission_id_permission_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_permission_role_id_permission_id_pk": {
+ "name": "role_permission_role_id_permission_id_pk",
+ "columns": ["role_id", "permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assignedAt": {
+ "name": "assignedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "userRole_role_idx": {
+ "name": "userRole_role_idx",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_role_user_id_user_id_fk": {
+ "name": "user_role_user_id_user_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_role_role_id_role_id_fk": {
+ "name": "user_role_role_id_role_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_role_user_id_role_id_pk": {
+ "name": "user_role_user_id_role_id_pk",
+ "columns": ["user_id", "role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/0006_snapshot.json b/drizzle/meta/0006_snapshot.json
new file mode 100644
index 0000000..2a9f965
--- /dev/null
+++ b/drizzle/meta/0006_snapshot.json
@@ -0,0 +1,2247 @@
+{
+ "id": "219ad6aa-b78e-444a-8d4e-cddd7b31830a",
+ "prevId": "38e3db88-4f08-43e5-96b0-0c9d8264b0a5",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_issuer_subject_uidx": {
+ "name": "account_issuer_subject_uidx",
+ "columns": [
+ {
+ "expression": "issuer",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.jwks": {
+ "name": "jwks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alg": {
+ "name": "alg",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "crv": {
+ "name": "crv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_access_token": {
+ "name": "oauth_access_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_id": {
+ "name": "refresh_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthAccessToken_clientId_idx": {
+ "name": "oauthAccessToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_sessionId_idx": {
+ "name": "oauthAccessToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_userId_idx": {
+ "name": "oauthAccessToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_authorizationCodeId_idx": {
+ "name": "oauthAccessToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_refreshId_idx": {
+ "name": "oauthAccessToken_refreshId_idx",
+ "columns": [
+ {
+ "expression": "refresh_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_access_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_access_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_session_id_session_id_fk": {
+ "name": "oauth_access_token_session_id_session_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_user_id_user_id_fk": {
+ "name": "oauth_access_token_user_id_user_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": {
+ "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_refresh_token",
+ "columnsFrom": ["refresh_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_access_token_token_unique": {
+ "name": "oauth_access_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client": {
+ "name": "oauth_client",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_discovery_id": {
+ "name": "client_discovery_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "skip_consent": {
+ "name": "skip_consent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_end_session": {
+ "name": "enable_end_session",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subject_type": {
+ "name": "subject_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_credentials_scopes": {
+ "name": "client_credentials_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uri": {
+ "name": "uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contacts": {
+ "name": "contacts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tos": {
+ "name": "tos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_id": {
+ "name": "software_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_version": {
+ "name": "software_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_statement": {
+ "name": "software_statement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "post_logout_redirect_uris": {
+ "name": "post_logout_redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_uri": {
+ "name": "backchannel_logout_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_session_required": {
+ "name": "backchannel_logout_session_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_endpoint_auth_method": {
+ "name": "token_endpoint_auth_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_type": {
+ "name": "application_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks": {
+ "name": "jwks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks_uri": {
+ "name": "jwks_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "grant_types": {
+ "name": "grant_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response_types": {
+ "name": "response_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "require_pkce": {
+ "name": "require_pkce",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens": {
+ "name": "dpop_bound_access_tokens",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClient_userId_idx": {
+ "name": "oauthClient_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_user_id_user_id_fk": {
+ "name": "oauth_client_user_id_user_id_fk",
+ "tableFrom": "oauth_client",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_client_client_id_unique": {
+ "name": "oauth_client_client_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["client_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_assertion": {
+ "name": "oauth_client_assertion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_resource": {
+ "name": "oauth_client_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClientResource_clientId_resourceId_uidx": {
+ "name": "oauthClientResource_clientId_resourceId_uidx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_clientId_idx": {
+ "name": "oauthClientResource_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_resourceId_idx": {
+ "name": "oauthClientResource_resourceId_idx",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_resource_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_client_resource_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_client_resource_resource_id_oauth_resource_identifier_fk": {
+ "name": "oauth_client_resource_resource_id_oauth_resource_identifier_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["identifier"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_consent": {
+ "name": "oauth_consent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthConsent_clientId_idx": {
+ "name": "oauthConsent_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthConsent_userId_idx": {
+ "name": "oauthConsent_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_consent_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_consent_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_consent_user_id_user_id_fk": {
+ "name": "oauth_consent_user_id_user_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_refresh_token": {
+ "name": "oauth_refresh_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotated_at": {
+ "name": "rotated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_response": {
+ "name": "rotation_replay_response",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_expires_at": {
+ "name": "rotation_replay_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_time": {
+ "name": "auth_time",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthRefreshToken_clientId_idx": {
+ "name": "oauthRefreshToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_sessionId_idx": {
+ "name": "oauthRefreshToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_userId_idx": {
+ "name": "oauthRefreshToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_authorizationCodeId_idx": {
+ "name": "oauthRefreshToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_refresh_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_session_id_session_id_fk": {
+ "name": "oauth_refresh_token_session_id_session_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_user_id_user_id_fk": {
+ "name": "oauth_refresh_token_user_id_user_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_refresh_token_token_unique": {
+ "name": "oauth_refresh_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_resource": {
+ "name": "oauth_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token_ttl": {
+ "name": "access_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_ttl": {
+ "name": "refresh_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_algorithm": {
+ "name": "signing_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_key_id": {
+ "name": "signing_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_scopes": {
+ "name": "allowed_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_claims": {
+ "name": "custom_claims",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens_required": {
+ "name": "dpop_bound_access_tokens_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy_version": {
+ "name": "policy_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_resource_identifier_unique": {
+ "name": "oauth_resource_identifier_unique",
+ "nullsNotDistinct": false,
+ "columns": ["identifier"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.passkey": {
+ "name": "passkey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "aaguid": {
+ "name": "aaguid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "passkey_userId_idx": {
+ "name": "passkey_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "passkey_user_id_user_id_fk": {
+ "name": "passkey_user_id_user_id_fk",
+ "tableFrom": "passkey",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "passkey_credential_id_unique": {
+ "name": "passkey_credential_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["credential_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit": {
+ "name": "rate_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "rate_limit_key_unique": {
+ "name": "rate_limit_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_evidence": {
+ "name": "identity_evidence",
+ "schema": "",
+ "columns": {
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "states": {
+ "name": "states",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "valid_until": {
+ "name": "valid_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "telegram_id": {
+ "name": "telegram_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "identity_evidence_issuer_subject_pk": {
+ "name": "identity_evidence_issuer_subject_pk",
+ "columns": ["issuer", "subject"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.student_verification_challenge": {
+ "name": "student_verification_challenge",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_sent_at": {
+ "name": "last_sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "studentVerificationChallenge_email_uidx": {
+ "name": "studentVerificationChallenge_email_uidx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "studentVerificationChallenge_expiresAt_idx": {
+ "name": "studentVerificationChallenge_expiresAt_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "student_verification_challenge_user_id_user_id_fk": {
+ "name": "student_verification_challenge_user_id_user_id_fk",
+ "tableFrom": "student_verification_challenge",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission": {
+ "name": "permission",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_key_uidx": {
+ "name": "permission_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_implication": {
+ "name": "permission_implication",
+ "schema": "",
+ "columns": {
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "implied_permission_id": {
+ "name": "implied_permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "permissionImplication_implied_idx": {
+ "name": "permissionImplication_implied_idx",
+ "columns": [
+ {
+ "expression": "implied_permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_implication_permission_id_permission_id_fk": {
+ "name": "permission_implication_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_implication_implied_permission_id_permission_id_fk": {
+ "name": "permission_implication_implied_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["implied_permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "permission_implication_permission_id_implied_permission_id_pk": {
+ "name": "permission_implication_permission_id_implied_permission_id_pk",
+ "columns": ["permission_id", "implied_permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "source_state": {
+ "name": "source_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "role_key_uidx": {
+ "name": "role_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_parent": {
+ "name": "role_parent",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_role_id": {
+ "name": "parent_role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "roleParent_parent_idx": {
+ "name": "roleParent_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_parent_role_id_role_id_fk": {
+ "name": "role_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_parent_parent_role_id_role_id_fk": {
+ "name": "role_parent_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["parent_role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_parent_role_id_parent_role_id_pk": {
+ "name": "role_parent_role_id_parent_role_id_pk",
+ "columns": ["role_id", "parent_role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_permission": {
+ "name": "role_permission",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "rolePermission_permission_idx": {
+ "name": "rolePermission_permission_idx",
+ "columns": [
+ {
+ "expression": "permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_permission_role_id_role_id_fk": {
+ "name": "role_permission_role_id_role_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_permission_permission_id_permission_id_fk": {
+ "name": "role_permission_permission_id_permission_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_permission_role_id_permission_id_pk": {
+ "name": "role_permission_role_id_permission_id_pk",
+ "columns": ["role_id", "permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assignedAt": {
+ "name": "assignedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "userRole_role_idx": {
+ "name": "userRole_role_idx",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_role_user_id_user_id_fk": {
+ "name": "user_role_user_id_user_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_role_role_id_role_id_fk": {
+ "name": "user_role_role_id_role_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_role_user_id_role_id_pk": {
+ "name": "user_role_user_id_role_id_pk",
+ "columns": ["user_id", "role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/0007_snapshot.json b/drizzle/meta/0007_snapshot.json
new file mode 100644
index 0000000..a5d8838
--- /dev/null
+++ b/drizzle/meta/0007_snapshot.json
@@ -0,0 +1,2303 @@
+{
+ "id": "163aaf0e-49be-401c-9cee-918d5f96c037",
+ "prevId": "219ad6aa-b78e-444a-8d4e-cddd7b31830a",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.account": {
+ "name": "account",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token": {
+ "name": "access_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token": {
+ "name": "refresh_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "id_token": {
+ "name": "id_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "access_token_expires_at": {
+ "name": "access_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_expires_at": {
+ "name": "refresh_token_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scope": {
+ "name": "scope",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "password": {
+ "name": "password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "account_userId_idx": {
+ "name": "account_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "account_issuer_subject_uidx": {
+ "name": "account_issuer_subject_uidx",
+ "columns": [
+ {
+ "expression": "issuer",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "account_user_id_user_id_fk": {
+ "name": "account_user_id_user_id_fk",
+ "tableFrom": "account",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.jwks": {
+ "name": "jwks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "private_key": {
+ "name": "private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "alg": {
+ "name": "alg",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "crv": {
+ "name": "crv",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_access_token": {
+ "name": "oauth_access_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_id": {
+ "name": "refresh_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthAccessToken_clientId_idx": {
+ "name": "oauthAccessToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_sessionId_idx": {
+ "name": "oauthAccessToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_userId_idx": {
+ "name": "oauthAccessToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_authorizationCodeId_idx": {
+ "name": "oauthAccessToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthAccessToken_refreshId_idx": {
+ "name": "oauthAccessToken_refreshId_idx",
+ "columns": [
+ {
+ "expression": "refresh_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_access_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_access_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_session_id_session_id_fk": {
+ "name": "oauth_access_token_session_id_session_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_user_id_user_id_fk": {
+ "name": "oauth_access_token_user_id_user_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_access_token_refresh_id_oauth_refresh_token_id_fk": {
+ "name": "oauth_access_token_refresh_id_oauth_refresh_token_id_fk",
+ "tableFrom": "oauth_access_token",
+ "tableTo": "oauth_refresh_token",
+ "columnsFrom": ["refresh_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_access_token_token_unique": {
+ "name": "oauth_access_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client": {
+ "name": "oauth_client",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret": {
+ "name": "client_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_discovery_id": {
+ "name": "client_discovery_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "skip_consent": {
+ "name": "skip_consent",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "enable_end_session": {
+ "name": "enable_end_session",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "subject_type": {
+ "name": "subject_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "client_credentials_scopes": {
+ "name": "client_credentials_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false,
+ "default": "'{}'"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "uri": {
+ "name": "uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "icon": {
+ "name": "icon",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "contacts": {
+ "name": "contacts",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tos": {
+ "name": "tos",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy": {
+ "name": "policy",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_id": {
+ "name": "software_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_version": {
+ "name": "software_version",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "software_statement": {
+ "name": "software_statement",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "post_logout_redirect_uris": {
+ "name": "post_logout_redirect_uris",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_uri": {
+ "name": "backchannel_logout_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "backchannel_logout_session_required": {
+ "name": "backchannel_logout_session_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "token_endpoint_auth_method": {
+ "name": "token_endpoint_auth_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "application_type": {
+ "name": "application_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks": {
+ "name": "jwks",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "jwks_uri": {
+ "name": "jwks_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "grant_types": {
+ "name": "grant_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "response_types": {
+ "name": "response_types",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "require_pkce": {
+ "name": "require_pkce",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens": {
+ "name": "dpop_bound_access_tokens",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClient_userId_idx": {
+ "name": "oauthClient_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_user_id_user_id_fk": {
+ "name": "oauth_client_user_id_user_id_fk",
+ "tableFrom": "oauth_client",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_client_client_id_unique": {
+ "name": "oauth_client_client_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["client_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_assertion": {
+ "name": "oauth_client_assertion",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_client_resource": {
+ "name": "oauth_client_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "resource_id": {
+ "name": "resource_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "oauthClientResource_clientId_resourceId_uidx": {
+ "name": "oauthClientResource_clientId_resourceId_uidx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_clientId_idx": {
+ "name": "oauthClientResource_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthClientResource_resourceId_idx": {
+ "name": "oauthClientResource_resourceId_idx",
+ "columns": [
+ {
+ "expression": "resource_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_client_resource_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_client_resource_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_client_resource_resource_id_oauth_resource_identifier_fk": {
+ "name": "oauth_client_resource_resource_id_oauth_resource_identifier_fk",
+ "tableFrom": "oauth_client_resource",
+ "tableTo": "oauth_resource",
+ "columnsFrom": ["resource_id"],
+ "columnsTo": ["identifier"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_consent": {
+ "name": "oauth_consent",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthConsent_clientId_idx": {
+ "name": "oauthConsent_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthConsent_userId_idx": {
+ "name": "oauthConsent_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_consent_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_consent_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_consent_user_id_user_id_fk": {
+ "name": "oauth_consent_user_id_user_id_fk",
+ "tableFrom": "oauth_consent",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_refresh_token": {
+ "name": "oauth_refresh_token",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "session_id": {
+ "name": "session_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "reference_id": {
+ "name": "reference_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "resources": {
+ "name": "resources",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "requested_user_info_claims": {
+ "name": "requested_user_info_claims",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked": {
+ "name": "revoked",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotated_at": {
+ "name": "rotated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_response": {
+ "name": "rotation_replay_response",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rotation_replay_expires_at": {
+ "name": "rotation_replay_expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "auth_time": {
+ "name": "auth_time",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "confirmation": {
+ "name": "confirmation",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "oauthRefreshToken_clientId_idx": {
+ "name": "oauthRefreshToken_clientId_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_sessionId_idx": {
+ "name": "oauthRefreshToken_sessionId_idx",
+ "columns": [
+ {
+ "expression": "session_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_userId_idx": {
+ "name": "oauthRefreshToken_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauthRefreshToken_authorizationCodeId_idx": {
+ "name": "oauthRefreshToken_authorizationCodeId_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_refresh_token_client_id_oauth_client_client_id_fk": {
+ "name": "oauth_refresh_token_client_id_oauth_client_client_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "oauth_client",
+ "columnsFrom": ["client_id"],
+ "columnsTo": ["client_id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_session_id_session_id_fk": {
+ "name": "oauth_refresh_token_session_id_session_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "session",
+ "columnsFrom": ["session_id"],
+ "columnsTo": ["id"],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "oauth_refresh_token_user_id_user_id_fk": {
+ "name": "oauth_refresh_token_user_id_user_id_fk",
+ "tableFrom": "oauth_refresh_token",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_refresh_token_token_unique": {
+ "name": "oauth_refresh_token_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_resource": {
+ "name": "oauth_resource",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "access_token_ttl": {
+ "name": "access_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "refresh_token_ttl": {
+ "name": "refresh_token_ttl",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_algorithm": {
+ "name": "signing_algorithm",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "signing_key_id": {
+ "name": "signing_key_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "allowed_scopes": {
+ "name": "allowed_scopes",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "custom_claims": {
+ "name": "custom_claims",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "dpop_bound_access_tokens_required": {
+ "name": "dpop_bound_access_tokens_required",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "disabled": {
+ "name": "disabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": false,
+ "default": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "policy_version": {
+ "name": "policy_version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false,
+ "default": 1
+ },
+ "metadata": {
+ "name": "metadata",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "oauth_resource_identifier_unique": {
+ "name": "oauth_resource_identifier_unique",
+ "nullsNotDistinct": false,
+ "columns": ["identifier"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.passkey": {
+ "name": "passkey",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_key": {
+ "name": "public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "credential_id": {
+ "name": "credential_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counter": {
+ "name": "counter",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_type": {
+ "name": "device_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "backed_up": {
+ "name": "backed_up",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "transports": {
+ "name": "transports",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "aaguid": {
+ "name": "aaguid",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "passkey_userId_idx": {
+ "name": "passkey_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "passkey_user_id_user_id_fk": {
+ "name": "passkey_user_id_user_id_fk",
+ "tableFrom": "passkey",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "passkey_credential_id_unique": {
+ "name": "passkey_credential_id_unique",
+ "nullsNotDistinct": false,
+ "columns": ["credential_id"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rate_limit": {
+ "name": "rate_limit",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "count": {
+ "name": "count",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_request": {
+ "name": "last_request",
+ "type": "bigint",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "rate_limit_key_unique": {
+ "name": "rate_limit_key_unique",
+ "nullsNotDistinct": false,
+ "columns": ["key"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.session": {
+ "name": "session",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token": {
+ "name": "token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_agent": {
+ "name": "user_agent",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "session_userId_idx": {
+ "name": "session_userId_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "session_user_id_user_id_fk": {
+ "name": "session_user_id_user_id_fk",
+ "tableFrom": "session",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "session_token_unique": {
+ "name": "session_token_unique",
+ "nullsNotDistinct": false,
+ "columns": ["token"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user": {
+ "name": "user",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email_verified": {
+ "name": "email_verified",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "image": {
+ "name": "image",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "user_email_unique": {
+ "name": "user_email_unique",
+ "nullsNotDistinct": false,
+ "columns": ["email"]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.verification": {
+ "name": "verification",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "identifier": {
+ "name": "identifier",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "value": {
+ "name": "value",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "verification_identifier_idx": {
+ "name": "verification_identifier_idx",
+ "columns": [
+ {
+ "expression": "identifier",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.identity_evidence": {
+ "name": "identity_evidence",
+ "schema": "",
+ "columns": {
+ "issuer": {
+ "name": "issuer",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subject": {
+ "name": "subject",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "provider_id": {
+ "name": "provider_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "external_id": {
+ "name": "external_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "states": {
+ "name": "states",
+ "type": "text[]",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'"
+ },
+ "valid_until": {
+ "name": "valid_until",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "telegram_id": {
+ "name": "telegram_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {
+ "identity_evidence_issuer_subject_pk": {
+ "name": "identity_evidence_issuer_subject_pk",
+ "columns": ["issuer", "subject"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.student_verification_challenge": {
+ "name": "student_verification_challenge",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "attempts": {
+ "name": "attempts",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_sent_at": {
+ "name": "last_sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "studentVerificationChallenge_email_uidx": {
+ "name": "studentVerificationChallenge_email_uidx",
+ "columns": [
+ {
+ "expression": "email",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "studentVerificationChallenge_expiresAt_idx": {
+ "name": "studentVerificationChallenge_expiresAt_idx",
+ "columns": [
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "student_verification_challenge_user_id_user_id_fk": {
+ "name": "student_verification_challenge_user_id_user_id_fk",
+ "tableFrom": "student_verification_challenge",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission": {
+ "name": "permission",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "permission_key_uidx": {
+ "name": "permission_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.permission_implication": {
+ "name": "permission_implication",
+ "schema": "",
+ "columns": {
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "implied_permission_id": {
+ "name": "implied_permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "permissionImplication_implied_idx": {
+ "name": "permissionImplication_implied_idx",
+ "columns": [
+ {
+ "expression": "implied_permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "permission_implication_permission_id_permission_id_fk": {
+ "name": "permission_implication_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "permission_implication_implied_permission_id_permission_id_fk": {
+ "name": "permission_implication_implied_permission_id_permission_id_fk",
+ "tableFrom": "permission_implication",
+ "tableTo": "permission",
+ "columnsFrom": ["implied_permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "permission_implication_permission_id_implied_permission_id_pk": {
+ "name": "permission_implication_permission_id_implied_permission_id_pk",
+ "columns": ["permission_id", "implied_permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.rbac_audit_event": {
+ "name": "rbac_audit_event",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "actor_id": {
+ "name": "actor_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "operation": {
+ "name": "operation",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "target_id": {
+ "name": "target_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "before": {
+ "name": "before",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "after": {
+ "name": "after",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true
+ },
+ "key": {
+ "name": "key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "managed": {
+ "name": "managed",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "source_state": {
+ "name": "source_state",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "createdAt": {
+ "name": "createdAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updatedAt": {
+ "name": "updatedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "role_key_uidx": {
+ "name": "role_key_uidx",
+ "columns": [
+ {
+ "expression": "key",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_parent": {
+ "name": "role_parent",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "parent_role_id": {
+ "name": "parent_role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "roleParent_parent_idx": {
+ "name": "roleParent_parent_idx",
+ "columns": [
+ {
+ "expression": "parent_role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_parent_role_id_role_id_fk": {
+ "name": "role_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_parent_parent_role_id_role_id_fk": {
+ "name": "role_parent_parent_role_id_role_id_fk",
+ "tableFrom": "role_parent",
+ "tableTo": "role",
+ "columnsFrom": ["parent_role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_parent_role_id_parent_role_id_pk": {
+ "name": "role_parent_role_id_parent_role_id_pk",
+ "columns": ["role_id", "parent_role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.role_permission": {
+ "name": "role_permission",
+ "schema": "",
+ "columns": {
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "permission_id": {
+ "name": "permission_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "rolePermission_permission_idx": {
+ "name": "rolePermission_permission_idx",
+ "columns": [
+ {
+ "expression": "permission_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "role_permission_role_id_role_id_fk": {
+ "name": "role_permission_role_id_role_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "role_permission_permission_id_permission_id_fk": {
+ "name": "role_permission_permission_id_permission_id_fk",
+ "tableFrom": "role_permission",
+ "tableTo": "permission",
+ "columnsFrom": ["permission_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "role_permission_role_id_permission_id_pk": {
+ "name": "role_permission_role_id_permission_id_pk",
+ "columns": ["role_id", "permission_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.user_role": {
+ "name": "user_role",
+ "schema": "",
+ "columns": {
+ "user_id": {
+ "name": "user_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role_id": {
+ "name": "role_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "assigned_by": {
+ "name": "assigned_by",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "assignedAt": {
+ "name": "assignedAt",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "userRole_role_idx": {
+ "name": "userRole_role_idx",
+ "columns": [
+ {
+ "expression": "role_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "user_role_user_id_user_id_fk": {
+ "name": "user_role_user_id_user_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "user",
+ "columnsFrom": ["user_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "user_role_role_id_role_id_fk": {
+ "name": "user_role_role_id_role_id_fk",
+ "tableFrom": "user_role",
+ "tableTo": "role",
+ "columnsFrom": ["role_id"],
+ "columnsTo": ["id"],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "user_role_user_id_role_id_pk": {
+ "name": "user_role_user_id_role_id_pk",
+ "columns": ["user_id", "role_id"]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {},
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 3ccd586..c99d0e9 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -29,6 +29,34 @@
"when": 1788738508125,
"tag": "0003_awesome_edwin_jarvis",
"breakpoints": true
+ },
+ {
+ "idx": 4,
+ "version": "7",
+ "when": 1789442003962,
+ "tag": "0004_overjoyed_mordo",
+ "breakpoints": true
+ },
+ {
+ "idx": 5,
+ "version": "7",
+ "when": 1789442009569,
+ "tag": "0005_left_lizard",
+ "breakpoints": true
+ },
+ {
+ "idx": 6,
+ "version": "7",
+ "when": 1789444594426,
+ "tag": "0006_volatile_pandemic",
+ "breakpoints": true
+ },
+ {
+ "idx": 7,
+ "version": "7",
+ "when": 1789650344269,
+ "tag": "0007_mushy_the_fury",
+ "breakpoints": true
}
]
}
diff --git a/scripts/security-config.d.mts b/scripts/security-config.d.mts
new file mode 100644
index 0000000..d18314e
--- /dev/null
+++ b/scripts/security-config.d.mts
@@ -0,0 +1,3 @@
+export function validateSecurityConfiguration(
+ environment: Record
+ {error} +
+ ) : hint ? ( ++ {hint} +
+ ) : null} +
+ {children}
+
+ );
+}
diff --git a/src/components/rbac/permission-form.tsx b/src/components/rbac/permission-form.tsx
new file mode 100644
index 0000000..38d8cd0
--- /dev/null
+++ b/src/components/rbac/permission-form.tsx
@@ -0,0 +1,214 @@
+import { useId, useState } from "react";
+import { LoaderCircle } from "lucide-react";
+import {
+ MAX_DESCRIPTION_LENGTH,
+ MAX_NAME_LENGTH,
+ type PermissionDraft,
+ type RbacCatalog,
+ type RbacDraftErrors,
+ expandPermissionKeys,
+ hasDraftErrors,
+ intrinsicImplications,
+ normalizePermissionDraft,
+ permissionImplicationWouldCycle,
+ validatePermissionDraft,
+} from "@/auth/rbac";
+import { Field, KeyChip } from "@/components/rbac/fields";
+import { useIdpAccessContext } from "@/components/idp-access";
+import { canGrantPermission } from "@/components/rbac/delegation";
+import { PickList } from "@/components/rbac/pick-list";
+import { useDraftErrors } from "@/components/rbac/use-draft-errors";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+
+export function PermissionForm({
+ mode,
+ initial,
+ catalog,
+ currentKey,
+ managed,
+ readOnly,
+ busy,
+ serverErrors,
+ submitLabel,
+ onSubmit,
+ onCancel,
+}: {
+ mode: "create" | "edit";
+ initial: PermissionDraft;
+ catalog: RbacCatalog;
+ /** The stored key when editing, so self-reference and cycles can be ruled out. */
+ currentKey?: string;
+ /** Built-in permissions keep their key: the identity provider checks for it by name. */
+ managed?: boolean;
+ /** Shown to someone who may see permissions but not change them. */
+ readOnly?: boolean;
+ busy: boolean;
+ serverErrors?: RbacDraftErrors;
+ submitLabel: string;
+ onSubmit: (draft: PermissionDraft) => void;
+ onCancel?: () => void;
+}) {
+ const access = useIdpAccessContext();
+ const [draft, setDraft] = useState(initial);
+ const [touched, setTouched] = useState(false);
+ const ids = { key: useId(), name: useId(), description: useId() };
+ const local = validatePermissionDraft(normalizePermissionDraft(draft), { catalog, currentKey });
+ const { serverFieldErrors, noteEdited } = useDraftErrors(serverErrors);
+ const errors: RbacDraftErrors = { ...serverFieldErrors, ...(touched ? local : {}) };
+ const change = (patch: Partial
+ It needs the {permission} permission. Ask an
+ administrator to give you a role that grants it.
+
{name}
+{email}
++ {error} +
+ )} + {canSearch && ( +Searching…
+ ) : candidates.length === 0 ? ( ++ {results.length + ? "Everyone matching already holds this role." + : "Nobody matches."} +
+ ) : ( + candidates.map((person) => ( +Loading members…
+ ) : members.length === 0 ? ( ++ {canSearch && cursors.length === 1 + ? `Nobody holds ${roleName} yet. Search above to give it to someone.` + : `Nobody holds ${roleName} yet.`} +
+ ) : ( +Loading the permission…
+ ); + } + if (loadError || !permission) { + return ( +{loadError || "This permission no longer exists."}
+
+
+ {error} +
+ )} + {saved && !error && ( ++ Saved. New sign-ins use this immediately; tokens already issued expire within minutes. +
+ )} + ++ {permission.managed + ? "Only Master Admin can change a built-in permission." + : "You can change only permissions you already hold, including everything they grant. Ask Master Admin for access."} +
+ )} ++ No role grants this permission yet, so nobody holds it.{" "} + {permission.managed && + "Whoever the deployment configures as an administrator holds it anyway, through Master Admin."} +
+ ) : ( +
+
+ The things an application can check for. They reach applications as the{" "}
+ polinetwork_permissions claim; each
+ application still enforces its own.
+
+ Name the first thing one of your applications should be able to check for, + such as reading the member list. +
++ Applications check for the key you choose here, so pick one you can live with. +
++ {error} +
+ )} +Loading permissions…
+ ) : loadError ? ( ++ {loadError} +
++ Unable to load the permissions this one could grant. Try again before creating one. +
+Loading the role…
; + } + if (loadError || !role) { + return ( +{loadError || "This role no longer exists."}
+
+
+ Granted automatically. {inferred.evidence}{" "} + Nobody can be given or refused this role by hand. + {canWrite && " Choose what it grants below."} +
++ {error} +
+ )} + {saved && !error && ( ++ Saved. New sign-ins use these permissions immediately; tokens already issued expire within + minutes. +
+ )} + ++ {role.managed + ? "Only Master Admin can change a built-in role." + : "This role grants permissions you do not hold. Ask Master Admin to change it or manage its members."} +
+ )} ++ Membership is worked out for each person as their tokens are issued, so there is no + list to edit here. +
+ ) : ( +
+
+ A role is a named bundle of permissions. Applications read them from the{" "}
+ polinetwork:identity scope.
+
+ Create a role to bundle permissions and give them to specific people. +
++ Bundle permissions under one name, then give it to people from the role's page. +
++ {error} +
+ )} +Loading permissions…
+ ) : loadError ? ( ++ {loadError} +
++ Unable to load the permissions this role could grant. Try again before creating one. +
++ Access requires role or permission administration access. Ask an administrator to grant the + appropriate role. +
++ Loading your session… +
+Unable to load your session. Please try again.
+We couldn't check whether you can manage roles.
++ Checking your access… +
+ )} + +- Prefer a pause? Disable the application instead; it can be re-enabled anytime. -
-+ Prefer a pause? Disable the application instead; it can be re-enabled anytime. +
+