refactor(base): SimpleLogin's forwarding page on the OpenID Connect module (ADR 0023)
halkyon_base depends on halkyon_oidc_server, and its forwarding page is now the module's (`addOidcCallbackForwarding`) with Halkyon's table of apps and its page in English or German; its tests keep what is Halkyon's own, the rest is tested in the module. Every host now includes the module: migrations `…-oidc-module` in halkyon_base, Learn and Todo create its two tables (no data touched) and record its version; Learn's devices get them empty, as they have halkyon_base's server-only tables. halkyon_base_client depends on halkyon_oidc_client, whose models its generated code uses, and Learn's server image copies both packages into its narrowed workspace. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
f549fa59df
commit
620ad270db
9 changed files with 43572 additions and 408 deletions
|
|
@ -16,15 +16,19 @@ COPY tool/workspace_only.sh tool/
|
|||
COPY apps/learn/halkyon_learn_server apps/learn/halkyon_learn_server
|
||||
COPY packages/halkyon_base_server packages/halkyon_base_server
|
||||
COPY packages/halkyon_base_client packages/halkyon_base_client
|
||||
COPY packages/halkyon_oidc_server packages/halkyon_oidc_server
|
||||
COPY packages/halkyon_oidc_client packages/halkyon_oidc_client
|
||||
|
||||
# The workspace root with only the server and the module it builds on: neither the
|
||||
# The workspace root with only the server and the modules it builds on (halkyon_base,
|
||||
# and the OpenID Connect module halkyon_base signs in with, ADR 0023): neither the
|
||||
# other apps nor Learn's client package are needed to build the server, and the Flutter
|
||||
# packages would need Flutter. The module's client comes along only because the
|
||||
# packages would need Flutter. The base module's client comes along only because the
|
||||
# module's tests depend on it (SRP's two halves, held to each other), and pub resolves
|
||||
# every member's dev dependencies; the server never imports it. Everything else stays
|
||||
# as the repository has it, above all the dependency overrides, which bring the server
|
||||
# our fork of the offline sync package (fetched with the git the Dart image has).
|
||||
RUN sh tool/workspace_only.sh apps/learn/halkyon_learn_server packages/halkyon_base_server packages/halkyon_base_client
|
||||
# every member's dev dependencies, the OpenID Connect module's client because that
|
||||
# client depends on it; the server never imports either. Everything else stays as the
|
||||
# repository has it, above all the dependency overrides, which bring the server our
|
||||
# fork of the offline sync package (fetched with the git the Dart image has).
|
||||
RUN sh tool/workspace_only.sh apps/learn/halkyon_learn_server packages/halkyon_base_server packages/halkyon_base_client packages/halkyon_oidc_server packages/halkyon_oidc_client
|
||||
RUN dart pub get
|
||||
WORKDIR /app/apps/learn/halkyon_learn_server
|
||||
RUN dart build cli --target bin/main.dart --output build
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import 'package:halkyon_learn_server/src/generated/explore/explore_query.dart'
|
|||
as _isbgipyv;
|
||||
import 'package:halkyon_learn_server/src/generated/future_calls.dart'
|
||||
as _iip9210p;
|
||||
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
as _iacs;
|
||||
|
|
@ -1272,6 +1273,8 @@ class Endpoints extends _is.EndpointDispatch {
|
|||
..initializeEndpoints(server);
|
||||
modules['serverpod_offline_sync'] = _izehhkf5.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['halkyon_oidc'] = _ipoqfiqr.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
modules['serverpod_auth_core'] = _iacs.Endpoints()
|
||||
..initializeEndpoints(server);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import 'package:halkyon_learn_server/src/generated/explore/explore_entry.dart'
|
|||
as _iscq690j;
|
||||
import 'package:halkyon_learn_server/src/generated/feedback/feedback_view.dart'
|
||||
as _iulci38e;
|
||||
import 'package:halkyon_oidc_server/halkyon_oidc_server.dart' as _ipoqfiqr;
|
||||
import 'package:serverpod/protocol.dart' as _isp;
|
||||
import 'package:serverpod/serverpod.dart' as _is;
|
||||
import 'package:serverpod_auth_core_server/serverpod_auth_core_server.dart'
|
||||
|
|
@ -3574,6 +3575,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
..._ik8molhd.Protocol.targetTableDefinitions,
|
||||
..._iais.Protocol.targetTableDefinitions,
|
||||
..._izehhkf5.Protocol.targetTableDefinitions,
|
||||
..._ipoqfiqr.Protocol.targetTableDefinitions,
|
||||
..._iacs.Protocol.targetTableDefinitions,
|
||||
..._isp.Protocol.targetTableDefinitions,
|
||||
];
|
||||
|
|
@ -4155,6 +4157,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
try {
|
||||
return _izehhkf5.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _ipoqfiqr.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().deserialize<T>(data, t);
|
||||
} on _is.DeserializationTypeNotFoundException catch (_) {}
|
||||
|
|
@ -4389,6 +4394,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
? className
|
||||
: 'serverpod_offline_sync.$className';
|
||||
}
|
||||
className = _ipoqfiqr.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.') ? className : 'halkyon_oidc.$className';
|
||||
}
|
||||
className = _iacs.Protocol().getClassNameForObject(data);
|
||||
if (className != null) {
|
||||
return className.contains('.')
|
||||
|
|
@ -4609,6 +4618,10 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
data['className'] = dataClassName.substring(23);
|
||||
return _izehhkf5.Protocol().deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName.startsWith('halkyon_oidc.')) {
|
||||
data['className'] = dataClassName.substring(13);
|
||||
return _ipoqfiqr.Protocol().deserializeByClassName(data);
|
||||
}
|
||||
if (dataClassName.startsWith('serverpod_auth_core.')) {
|
||||
data['className'] = dataClassName.substring(20);
|
||||
return _iacs.Protocol().deserializeByClassName(data);
|
||||
|
|
@ -4624,6 +4637,7 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
_ik8molhd.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iais.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_izehhkf5.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_ipoqfiqr.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
_iacs.Protocol().registerHostProtocol('halkyon_learn', this);
|
||||
}
|
||||
|
||||
|
|
@ -4647,6 +4661,12 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _ipoqfiqr.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
return table;
|
||||
}
|
||||
}
|
||||
{
|
||||
var table = _iacs.Protocol().getTableForType(t);
|
||||
if (table != null) {
|
||||
|
|
@ -4759,6 +4779,9 @@ class Protocol extends _is.DatabaseSerializationManager {
|
|||
try {
|
||||
return _izehhkf5.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _ipoqfiqr.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
try {
|
||||
return _iacs.Protocol().mapRecordToJson(record);
|
||||
} catch (_) {}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
|
@ -0,0 +1,229 @@
|
|||
{
|
||||
"__className__": "serverpod.DatabaseMigration",
|
||||
"actions": [
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "halkyon_oidc_attempt",
|
||||
"dartName": "OidcAttempt",
|
||||
"module": "halkyon_oidc",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "provider",
|
||||
"fieldName": "provider",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "state",
|
||||
"fieldName": "state",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "nonce",
|
||||
"fieldName": "nonce",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "codeVerifier",
|
||||
"fieldName": "codeVerifier",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "callerId",
|
||||
"fieldName": "callerId",
|
||||
"columnType": 7,
|
||||
"isNullable": true,
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "subject",
|
||||
"fieldName": "subject",
|
||||
"columnType": 0,
|
||||
"isNullable": true,
|
||||
"dartType": "String?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "expiresAt",
|
||||
"fieldName": "expiresAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "halkyon_oidc_attempt_fk_0",
|
||||
"columns": [
|
||||
"callerId"
|
||||
],
|
||||
"referenceTable": "serverpod_auth_core_user",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "halkyon_oidc_attempt_expires_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "expiresAt"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": false,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.DatabaseMigrationAction",
|
||||
"type": "createTable",
|
||||
"createTable": {
|
||||
"__className__": "serverpod.TableDefinition",
|
||||
"name": "halkyon_oidc_identity",
|
||||
"dartName": "OidcIdentity",
|
||||
"module": "halkyon_oidc",
|
||||
"schema": "public",
|
||||
"columns": [
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "id",
|
||||
"fieldName": "id",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"columnDefault": "random_v7",
|
||||
"dartType": "UuidValue?"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "authUserId",
|
||||
"fieldName": "authUserId",
|
||||
"columnType": 7,
|
||||
"isNullable": false,
|
||||
"dartType": "UuidValue"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "provider",
|
||||
"fieldName": "provider",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "subject",
|
||||
"fieldName": "subject",
|
||||
"columnType": 0,
|
||||
"isNullable": false,
|
||||
"dartType": "String"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.ColumnDefinition",
|
||||
"name": "createdAt",
|
||||
"fieldName": "createdAt",
|
||||
"columnType": 4,
|
||||
"isNullable": false,
|
||||
"dartType": "DateTime"
|
||||
}
|
||||
],
|
||||
"foreignKeys": [
|
||||
{
|
||||
"__className__": "serverpod.ForeignKeyDefinition",
|
||||
"constraintName": "halkyon_oidc_identity_fk_0",
|
||||
"columns": [
|
||||
"authUserId"
|
||||
],
|
||||
"referenceTable": "serverpod_auth_core_user",
|
||||
"referenceTableSchema": "public",
|
||||
"referenceColumns": [
|
||||
"id"
|
||||
],
|
||||
"onUpdate": 3,
|
||||
"onDelete": 4
|
||||
}
|
||||
],
|
||||
"indexes": [
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "halkyon_oidc_identity_subject_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "provider"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "subject"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexDefinition",
|
||||
"indexName": "halkyon_oidc_identity_person_idx",
|
||||
"elements": [
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "authUserId"
|
||||
},
|
||||
{
|
||||
"__className__": "serverpod.IndexElementDefinition",
|
||||
"type": 0,
|
||||
"definition": "provider"
|
||||
}
|
||||
],
|
||||
"type": "btree",
|
||||
"isUnique": true,
|
||||
"isPrimary": false
|
||||
}
|
||||
],
|
||||
"managed": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"warnings": [],
|
||||
"migrationApiVersion": 1
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
BEGIN;
|
||||
|
||||
--
|
||||
-- Function: gen_random_uuid_v7()
|
||||
-- Source: https://gist.github.com/kjmph/5bd772b2c2df145aa645b837da7eca74
|
||||
-- License: MIT (copyright notice included on the generator source code).
|
||||
--
|
||||
create or replace function gen_random_uuid_v7()
|
||||
returns uuid
|
||||
as $$
|
||||
begin
|
||||
-- use random v4 uuid as starting point (which has the same variant we need)
|
||||
-- then overlay timestamp
|
||||
-- then set version 7 by flipping the 2 and 1 bit in the version 4 string
|
||||
return encode(
|
||||
set_bit(
|
||||
set_bit(
|
||||
overlay(uuid_send(gen_random_uuid())
|
||||
placing substring(int8send(floor(extract(epoch from clock_timestamp()) * 1000)::bigint) from 3)
|
||||
from 1 for 6
|
||||
),
|
||||
52, 1
|
||||
),
|
||||
53, 1
|
||||
),
|
||||
'hex')::uuid;
|
||||
end
|
||||
$$
|
||||
language plpgsql
|
||||
volatile;
|
||||
|
||||
--
|
||||
-- ACTION CREATE TABLE
|
||||
--
|
||||
CREATE TABLE "halkyon_oidc_attempt" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
"provider" text NOT NULL,
|
||||
"state" text NOT NULL,
|
||||
"nonce" text NOT NULL,
|
||||
"codeVerifier" text,
|
||||
"callerId" uuid,
|
||||
"subject" text,
|
||||
"expiresAt" timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX "halkyon_oidc_attempt_expires_idx" ON "halkyon_oidc_attempt" USING btree ("expiresAt");
|
||||
|
||||
--
|
||||
-- ACTION CREATE TABLE
|
||||
--
|
||||
CREATE TABLE "halkyon_oidc_identity" (
|
||||
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid_v7(),
|
||||
"authUserId" uuid NOT NULL,
|
||||
"provider" text NOT NULL,
|
||||
"subject" text NOT NULL,
|
||||
"createdAt" timestamp without time zone NOT NULL
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE UNIQUE INDEX "halkyon_oidc_identity_subject_idx" ON "halkyon_oidc_identity" USING btree ("provider", "subject");
|
||||
CREATE UNIQUE INDEX "halkyon_oidc_identity_person_idx" ON "halkyon_oidc_identity" USING btree ("authUserId", "provider");
|
||||
|
||||
--
|
||||
-- ACTION CREATE FOREIGN KEY
|
||||
--
|
||||
ALTER TABLE ONLY "halkyon_oidc_attempt"
|
||||
ADD CONSTRAINT "halkyon_oidc_attempt_fk_0"
|
||||
FOREIGN KEY("callerId")
|
||||
REFERENCES "serverpod_auth_core_user"("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE NO ACTION;
|
||||
|
||||
--
|
||||
-- ACTION CREATE FOREIGN KEY
|
||||
--
|
||||
ALTER TABLE ONLY "halkyon_oidc_identity"
|
||||
ADD CONSTRAINT "halkyon_oidc_identity_fk_0"
|
||||
FOREIGN KEY("authUserId")
|
||||
REFERENCES "serverpod_auth_core_user"("id")
|
||||
ON DELETE CASCADE
|
||||
ON UPDATE NO ACTION;
|
||||
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_learn
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_learn', '20261023181810112-oidc-module', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261023181810112-oidc-module', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_base
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_base', '20261023181809816-oidc-module', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261023181809816-oidc-module', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod', '20260824182259319', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260824182259319', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_auth_idp
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_auth_idp', '20260924105404509', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260924105404509', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_offline_sync
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_offline_sync', '20260914143806119', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260914143806119', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR halkyon_oidc
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('halkyon_oidc', '20261023180644988', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20261023180644988', "timestamp" = now();
|
||||
|
||||
--
|
||||
-- MIGRATION VERSION FOR serverpod_auth_core
|
||||
--
|
||||
INSERT INTO "serverpod_migrations" ("module", "version", "timestamp")
|
||||
VALUES ('serverpod_auth_core', '20260924105232991', now())
|
||||
ON CONFLICT ("module")
|
||||
DO UPDATE SET "version" = '20260924105232991', "timestamp" = now();
|
||||
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -46,3 +46,4 @@
|
|||
20261021134803749-reset-hold-outs
|
||||
20261021184601197-private-studysets
|
||||
20261023131636437
|
||||
20261023181810112-oidc-module
|
||||
|
|
|
|||
Loading…
Reference in a new issue