You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
docs(sso): refresh RFC to reflect users table landing as #15
- Strike the "prerequisite blocker" section: the users table,
sessions.user_id and user_type='user' all landed in #15. Quote the
as-landed schema from src/lib/server/db/schema.ts rather than the
org_users shape originally proposed.
- Spell out the additive migration SSO still needs (auth_source,
sso_subject, last_login_at on users; partial unique index on
(org_id, sso_subject)). Drop role/status — out of scope for SSO.
- Update the auth-flow pseudocode to reference users (not org_users)
and to note last_login_at should be bumped on callback success.
- Strike chunk 2 in the rollout plan (landed as #15) and renumber
chunks 3-7 accordingly.
- Add postguard-business#15 to the "Related" links and flag the
refresh date in the status line.
No prose rewrite; no change to the auth flow, security considerations,
or open questions. The landed schema matched the proposal closely
enough that the plan survives intact — the drift is mostly additive.
Related: [postguard#74](https://github.com/encryption4all/postguard/issues/74) (org user management), [postguard#146](https://github.com/encryption4all/postguard/issues/146) (AD / SCIM sync)
5
+
Related: [postguard#74](https://github.com/encryption4all/postguard/issues/74) (org user management), [postguard#146](https://github.com/encryption4all/postguard/issues/146) (AD / SCIM sync), [postguard-business#15](https://github.com/encryption4all/postguard-business/pull/15) (users table — landed)
6
6
7
7
This document proposes the shape of SSO support in postguard-business before
8
8
any schema or code lands. The goal is to agree on the model and phasing so
@@ -34,35 +34,59 @@ authenticated, without re-proving identity attributes on every action.
34
34
-**Custom-branded login pages per org.** A plain `/auth/sso/[orgSlug]`
35
35
bounce is enough for v1.
36
36
37
-
## Prerequisite: multi-user organizations (blocks this work)
37
+
## Prerequisite: multi-user organizations — **landed in #15**
38
38
39
-
The current data model has one email per organization (`organizations.email`).
40
-
There is no `org_users` table — you cannot log in "as Alice from Acme", only
41
-
"as Acme". SSO only makes sense once an organization can have many users,
42
-
so this proposal assumes issue #74 (part A, data model) lands first or in
43
-
the same PR stack.
39
+
The original version of this proposal called this out as a blocker. It is
40
+
now done: PR [#15](https://github.com/encryption4all/postguard-business/pull/15)
41
+
landed a `users` table and the session plumbing needed to log in as a
42
+
specific person, not just "as the organization". SSO plugs into that same
43
+
table rather than introducing a second `org_users` table.
44
44
45
-
Proposed minimal `org_users` shape (owned by #74, stated here only to fix
46
-
the dependency):
45
+
As-landed shape (`src/lib/server/db/schema.ts`):
47
46
48
47
```
49
-
org_users (
48
+
users (
50
49
id uuid pk,
51
-
org_id uuid references organizations,
52
-
email varchar(256) not null,
53
-
full_name varchar(256),
54
-
role varchar(32) not null default 'member', -- 'owner' | 'admin' | 'member'
55
-
status varchar(32) not null default 'active', -- 'invited' | 'active' | 'disabled'
56
-
auth_source varchar(32) not null default 'yivi', -- 'yivi' | 'sso'
57
-
sso_subject varchar(256), -- IdP 'sub' claim, for SSO users
58
-
created_at timestamptz,
59
-
last_login_at timestamptz,
60
-
unique (org_id, email)
50
+
email varchar(256) not null unique, -- globally unique (not per-org)
51
+
full_name varchar(256) not null,
52
+
phone varchar(32),
53
+
org_id uuid not null references organizations on delete cascade,
54
+
created_at timestamptz not null default now(),
55
+
index idx_users_org (org_id)
61
56
)
62
57
```
63
58
64
-
`sessions.userType` gains `'user'` as a third value (alongside `'org'` and
65
-
`'admin'`) and `sessions.orgUserId` references `org_users.id`.
59
+
Sessions already carry `user_type` (`'org'` | `'admin'` | `'user'`) and
60
+
`user_id` referencing `users.id`. The SSO callback creates a session row
61
+
with `user_type='user'` the same way Yivi login does.
62
+
63
+
### Additive columns this proposal adds to `users`
64
+
65
+
SSO needs three columns that #15 did not land. They are additive, nullable,
66
+
and default to Yivi behaviour so existing rows keep working:
67
+
68
+
```
69
+
-- migration 0002_add-sso-fields.sql
70
+
alter table users
71
+
add column auth_source varchar(32) not null default 'yivi',
72
+
-- 'yivi' | 'sso'
73
+
add column sso_subject varchar(256),
74
+
-- IdP 'sub' claim — only set for auth_source='sso' users
75
+
add column last_login_at timestamptz;
76
+
77
+
create unique index users_sso_subject_unique
78
+
on users (org_id, sso_subject)
79
+
where sso_subject is not null;
80
+
```
81
+
82
+
`role` / `status` are intentionally NOT part of this migration — #15 did
83
+
not land them, and SSO does not need them (role gating is a follow-up for
84
+
the admin/members work). Adding them here would widen scope.
85
+
86
+
**Email uniqueness note.** The landed `users.email` is globally unique,
87
+
not per-org. That is fine for SSO: a single person works at one
88
+
organization in PostGuard. If that ever changes, both the landed schema
89
+
and this proposal have to evolve together.
66
90
67
91
## Data model for SSO
68
92
@@ -120,9 +144,11 @@ User → /api/auth/sso/callback?code=…&state=…
120
144
| 2. Exchange code at token endpoint
121
145
| 3. Verify ID token signature against JWKS, iss/aud/exp checks
122
146
| 4. Apply claim_mapping to extract email + sub + full_name
123
-
| 5. Find org_users by (org_id, sso_subject); if miss, find by
124
-
| (org_id, email); if miss and jit_provisioning, insert; else 403
0 commit comments