Repository navigation
feat(hub): establish identity transaction kernel #163
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
alliscode
wants to merge
8
commits into
build/hub/new
Choose a base branch
from
alliscode-identity-transaction-kernel
base: build/hub/new
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+3,128
−7
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
134d98b
feat(hub): add identity transaction kernel
c4f959b
fix(hub): harden identity foundation invariants
f87c1c7
test(hub): cover identity JSON limits
95d3a25
fix(hub): secure identity transaction boundaries
a7e8bcd
fix(hub): own identity retry and validation audit
c65562b
fix(hub): bound external identity index keys
babe4de
fix(hub): harden identity edge cases
0ca55ec
refactor(hub): align identity kernel boundaries
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| model AuthenticationProviderRevision { | ||
| // ID columns | ||
| id String @id @default(uuid()) @db.Uuid | ||
| providerId String @map("provider_id") @db.Uuid | ||
|
|
||
| // Relations | ||
| provider AuthenticationProvider @relation("AuthenticationProviderRevisions", fields: [providerId], references: [id], onDelete: Restrict) | ||
| selectedBy AuthenticationProvider? @relation("SelectedAuthenticationProviderRevision") | ||
|
|
||
| // Attribute columns | ||
| revision Int | ||
| configurationDigest String @map("configuration_digest") @db.VarChar(64) | ||
|
|
||
| // Timestamp columns | ||
| createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) | ||
| updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3) | ||
|
|
||
| @@unique([providerId, revision]) | ||
| @@index([providerId]) | ||
| @@map("authentication_provider_revisions") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| model AuthenticationProvider { | ||
| // ID columns | ||
| id String @id @default(uuid()) @db.Uuid | ||
| selectedRevisionId String? @unique @map("selected_revision_id") @db.Uuid | ||
|
|
||
| // Relations | ||
| revisions AuthenticationProviderRevision[] @relation("AuthenticationProviderRevisions") | ||
| selectedRevision AuthenticationProviderRevision? @relation("SelectedAuthenticationProviderRevision", fields: [selectedRevisionId], references: [id], onDelete: Restrict) | ||
|
|
||
| // Attribute columns | ||
| key String @unique @db.VarChar(100) | ||
|
|
||
| // Timestamp columns | ||
| createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) | ||
| updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3) | ||
|
|
||
| @@map("authentication_providers") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| model ExternalIdentity { | ||
| // ID columns | ||
| id String @id @default(uuid()) @db.Uuid | ||
| userId String @map("user_id") @db.Uuid | ||
|
|
||
| // Relations | ||
| user User @relation(fields: [userId], references: [id], onDelete: Restrict) | ||
|
|
||
| // Attribute columns | ||
| provider String @db.VarChar(100) | ||
| issuer String @db.VarChar(500) | ||
| subject String @db.VarChar(500) | ||
|
|
||
| // Timestamp columns | ||
| createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) | ||
| updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3) | ||
|
|
||
| @@unique([provider, issuer, subject]) | ||
| @@index([userId]) | ||
| @@map("external_identities") | ||
| } |
177 changes: 177 additions & 0 deletions
177
hub/schema/migrations/20261029203000_identity_foundation/migration.sql
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,177 @@ | ||
| CREATE TYPE "SecurityAuditOutcome" AS ENUM ('SUCCESS', 'DENIED', 'FAILURE'); | ||
|
|
||
| CREATE TABLE "users" ( | ||
| "id" UUID NOT NULL, | ||
| "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
|
|
||
| CONSTRAINT "users_pkey" PRIMARY KEY ("id") | ||
| ); | ||
|
|
||
| CREATE TABLE "external_identities" ( | ||
| "id" UUID NOT NULL, | ||
| "user_id" UUID NOT NULL, | ||
| "provider" VARCHAR(100) NOT NULL, | ||
| "issuer" VARCHAR(500) NOT NULL, | ||
| "subject" VARCHAR(500) NOT NULL, | ||
| "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
|
|
||
| CONSTRAINT "external_identities_pkey" PRIMARY KEY ("id"), | ||
| CONSTRAINT "external_identities_values_present" CHECK ( | ||
| length("provider") > 0 AND length("issuer") > 0 AND length("subject") > 0 | ||
| ), | ||
| CONSTRAINT "external_identities_tuple_index_bytes" CHECK ( | ||
| octet_length("provider") + octet_length("issuer") + octet_length("subject") + 2 <= 2000 | ||
| ) | ||
| ); | ||
|
|
||
| CREATE TABLE "authentication_providers" ( | ||
| "id" UUID NOT NULL, | ||
| "selected_revision_id" UUID, | ||
| "key" VARCHAR(100) NOT NULL, | ||
| "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
|
|
||
| CONSTRAINT "authentication_providers_pkey" PRIMARY KEY ("id"), | ||
| CONSTRAINT "authentication_providers_key_present" CHECK (length("key") > 0) | ||
| ); | ||
|
|
||
| CREATE TABLE "authentication_provider_revisions" ( | ||
| "id" UUID NOT NULL, | ||
| "provider_id" UUID NOT NULL, | ||
| "revision" INTEGER NOT NULL, | ||
| "configuration_digest" VARCHAR(64) NOT NULL, | ||
| "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
|
|
||
| CONSTRAINT "authentication_provider_revisions_pkey" PRIMARY KEY ("id"), | ||
| CONSTRAINT "authentication_provider_revisions_revision_positive" CHECK ("revision" > 0), | ||
| CONSTRAINT "authentication_provider_revisions_digest_sha256" CHECK ( | ||
| "configuration_digest" ~ '^[0-9a-f]{64}$' | ||
| ) | ||
| ); | ||
|
|
||
| CREATE TABLE "security_audits" ( | ||
| "id" UUID NOT NULL, | ||
| "action" VARCHAR(100) NOT NULL, | ||
| "outcome" "SecurityAuditOutcome", | ||
| "target_type" VARCHAR(100) NOT NULL, | ||
| "target_id" VARCHAR(100), | ||
| "context" JSONB NOT NULL, | ||
| "created_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
| "updated_at" TIMESTAMPTZ(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, | ||
|
|
||
| CONSTRAINT "security_audits_pkey" PRIMARY KEY ("id"), | ||
| CONSTRAINT "security_audits_values_present" CHECK ( | ||
| length("action") > 0 AND length("target_type") > 0 | ||
| ), | ||
| CONSTRAINT "security_audits_context_object" CHECK (jsonb_typeof("context") = 'object'), | ||
| CONSTRAINT "security_audits_context_bounded" CHECK (pg_column_size("context") <= 4096) | ||
| ); | ||
|
|
||
| CREATE UNIQUE INDEX "external_identities_provider_issuer_subject_key" | ||
| ON "external_identities"("provider", "issuer", "subject"); | ||
|
alliscode marked this conversation as resolved.
Resolved
|
||
|
|
||
| CREATE INDEX "external_identities_user_id_idx" ON "external_identities"("user_id"); | ||
|
|
||
| CREATE UNIQUE INDEX "authentication_providers_selected_revision_id_key" | ||
| ON "authentication_providers"("selected_revision_id"); | ||
|
|
||
| CREATE UNIQUE INDEX "authentication_providers_key_key" ON "authentication_providers"("key"); | ||
|
|
||
| CREATE UNIQUE INDEX "authentication_provider_revisions_provider_id_revision_key" | ||
| ON "authentication_provider_revisions"("provider_id", "revision"); | ||
|
|
||
| CREATE INDEX "authentication_provider_revisions_provider_id_idx" | ||
| ON "authentication_provider_revisions"("provider_id"); | ||
|
|
||
| CREATE INDEX "security_audits_action_created_at_idx" | ||
| ON "security_audits"("action", "created_at"); | ||
|
|
||
| ALTER TABLE "external_identities" | ||
| ADD CONSTRAINT "external_identities_user_id_fkey" | ||
| FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
|
||
| ALTER TABLE "authentication_provider_revisions" | ||
| ADD CONSTRAINT "authentication_provider_revisions_provider_id_fkey" | ||
| FOREIGN KEY ("provider_id") REFERENCES "authentication_providers"("id") | ||
| ON DELETE RESTRICT ON UPDATE CASCADE; | ||
|
|
||
| ALTER TABLE "authentication_providers" | ||
| ADD CONSTRAINT "authentication_providers_selected_revision_id_fkey" | ||
| FOREIGN KEY ("selected_revision_id") REFERENCES "authentication_provider_revisions"("id") | ||
| ON DELETE RESTRICT ON UPDATE CASCADE | ||
| DEFERRABLE INITIALLY DEFERRED; | ||
|
|
||
| CREATE FUNCTION prevent_external_identity_changes() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| BEGIN | ||
| RAISE EXCEPTION 'External identity records are immutable.'; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER "external_identities_immutable" | ||
| BEFORE UPDATE OR DELETE ON "external_identities" | ||
| FOR EACH ROW EXECUTE FUNCTION prevent_external_identity_changes(); | ||
|
|
||
| CREATE FUNCTION prevent_authentication_provider_revision_changes() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| BEGIN | ||
| RAISE EXCEPTION 'Authentication provider revisions are immutable.'; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER "authentication_provider_revisions_immutable" | ||
| BEFORE UPDATE OR DELETE ON "authentication_provider_revisions" | ||
| FOR EACH ROW EXECUTE FUNCTION prevent_authentication_provider_revision_changes(); | ||
|
|
||
| CREATE FUNCTION prevent_security_audit_changes() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| BEGIN | ||
| RAISE EXCEPTION 'Security audit records are immutable.'; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE TRIGGER "security_audits_immutable" | ||
| BEFORE UPDATE OR DELETE ON "security_audits" | ||
| FOR EACH ROW EXECUTE FUNCTION prevent_security_audit_changes(); | ||
|
|
||
| CREATE FUNCTION enforce_selected_revision_provider() | ||
| RETURNS trigger | ||
| LANGUAGE plpgsql | ||
| AS $$ | ||
| DECLARE | ||
| selected_revision UUID; | ||
| BEGIN | ||
| SELECT "selected_revision_id" | ||
| INTO selected_revision | ||
| FROM "authentication_providers" | ||
| WHERE "id" = NEW."id"; | ||
|
|
||
| IF selected_revision IS NULL THEN | ||
| RAISE EXCEPTION 'Authentication provider must have a selected revision.'; | ||
| END IF; | ||
|
|
||
| IF NOT EXISTS ( | ||
| SELECT 1 | ||
| FROM "authentication_provider_revisions" | ||
| WHERE "id" = selected_revision | ||
| AND "provider_id" = NEW."id" | ||
| ) THEN | ||
| RAISE EXCEPTION 'Selected revision must belong to its authentication provider.'; | ||
| END IF; | ||
| RETURN NEW; | ||
| END; | ||
| $$; | ||
|
|
||
| CREATE CONSTRAINT TRIGGER "authentication_provider_selected_revision_owner" | ||
| AFTER INSERT OR UPDATE OF "selected_revision_id" ON "authentication_providers" | ||
| DEFERRABLE INITIALLY DEFERRED | ||
| FOR EACH ROW EXECUTE FUNCTION enforce_selected_revision_provider(); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| provider = "postgresql" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| enum SecurityAuditOutcome { | ||
| SUCCESS | ||
| DENIED | ||
| FAILURE | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| model SecurityAudit { | ||
| // ID columns | ||
| id String @id @default(uuid()) @db.Uuid | ||
|
|
||
| // Attribute columns | ||
| action String @db.VarChar(100) | ||
| outcome SecurityAuditOutcome? | ||
| targetType String @map("target_type") @db.VarChar(100) | ||
| targetId String? @map("target_id") @db.VarChar(100) | ||
| context Json @db.JsonB | ||
|
|
||
| // Timestamp columns | ||
| createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) | ||
| updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3) | ||
|
|
||
| @@index([action, createdAt]) | ||
| @@map("security_audits") | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| model User { | ||
| // ID columns | ||
| id String @id @default(uuid()) @db.Uuid | ||
|
|
||
| // Relations | ||
| externalIdentities ExternalIdentity[] | ||
|
|
||
| // Timestamp columns | ||
| createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(3) | ||
| updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(3) | ||
|
|
||
| @@map("users") | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.