fix(auth): make JIT provisioning idempotent under concurrent first-login
On a user's first login the authed shell fires several API calls at once; each ran the JIT-provision path and raced to INSERT the same new user row, tripping the partial unique index on auth_subject. The losing requests 500'd and the post-login SSR errored out. Use INSERT ... ON CONFLICT (auth_subject) DO UPDATE ... RETURNING so concurrent callers converge on the row the winner created. Regenerated the .sqlx offline query cache. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
6823147334
commit
6071e1fd61
+33
@@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "INSERT INTO users (id, workspace_id, email, role, display_name, auth_subject)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (auth_subject) WHERE auth_subject IS NOT NULL\n DO UPDATE SET role = EXCLUDED.role\n RETURNING id, workspace_id",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"ordinal": 0,
|
||||||
|
"name": "id",
|
||||||
|
"type_info": "Uuid"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ordinal": 1,
|
||||||
|
"name": "workspace_id",
|
||||||
|
"type_info": "Uuid"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Uuid",
|
||||||
|
"Uuid",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Text",
|
||||||
|
"Text"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "3b52f6b5c493c72e79a88d223acafa4f02c582a9293ad2f72782e60136e6c287"
|
||||||
|
}
|
||||||
-19
@@ -1,19 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "PostgreSQL",
|
|
||||||
"query": "INSERT INTO users (id, workspace_id, email, role, display_name, auth_subject)\n VALUES ($1, $2, $3, $4, $5, $6)",
|
|
||||||
"describe": {
|
|
||||||
"columns": [],
|
|
||||||
"parameters": {
|
|
||||||
"Left": [
|
|
||||||
"Uuid",
|
|
||||||
"Uuid",
|
|
||||||
"Text",
|
|
||||||
"Text",
|
|
||||||
"Text",
|
|
||||||
"Text"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"nullable": []
|
|
||||||
},
|
|
||||||
"hash": "ff63ee4d5947897ef004654a80c4729c4824d2965b23dfe03ab8c59bce243a2e"
|
|
||||||
}
|
|
||||||
@@ -124,9 +124,17 @@ impl AuthService {
|
|||||||
.unwrap_or_else(|| format!("{}@sso.local", claims.sub));
|
.unwrap_or_else(|| format!("{}@sso.local", claims.sub));
|
||||||
let display_name = email.split('@').next().unwrap_or("teammate").to_owned();
|
let display_name = email.split('@').next().unwrap_or("teammate").to_owned();
|
||||||
let user_id = UserId::new();
|
let user_id = UserId::new();
|
||||||
sqlx::query!(
|
// Idempotent under concurrent first-login: the authed shell fires
|
||||||
|
// several API calls at once, and each would otherwise race to INSERT
|
||||||
|
// this row and trip the partial unique index on auth_subject (the
|
||||||
|
// losers 500'd). ON CONFLICT resolves to the row the winning request
|
||||||
|
// created, so every concurrent caller returns the same identity.
|
||||||
|
let row = sqlx::query!(
|
||||||
"INSERT INTO users (id, workspace_id, email, role, display_name, auth_subject)
|
"INSERT INTO users (id, workspace_id, email, role, display_name, auth_subject)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6)",
|
VALUES ($1, $2, $3, $4, $5, $6)
|
||||||
|
ON CONFLICT (auth_subject) WHERE auth_subject IS NOT NULL
|
||||||
|
DO UPDATE SET role = EXCLUDED.role
|
||||||
|
RETURNING id, workspace_id",
|
||||||
user_id.as_uuid(),
|
user_id.as_uuid(),
|
||||||
workspace,
|
workspace,
|
||||||
email,
|
email,
|
||||||
@@ -134,11 +142,11 @@ impl AuthService {
|
|||||||
display_name,
|
display_name,
|
||||||
claims.sub,
|
claims.sub,
|
||||||
)
|
)
|
||||||
.execute(&self.pool)
|
.fetch_one(&self.pool)
|
||||||
.await?;
|
.await?;
|
||||||
Ok(AuthedUser {
|
Ok(AuthedUser {
|
||||||
user_id,
|
user_id: UserId::from(row.id),
|
||||||
workspace_id: WorkspaceId::from(workspace),
|
workspace_id: WorkspaceId::from(row.workspace_id),
|
||||||
role,
|
role,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user