Username i auth_identities: Authentik-synk ved login (oppgave 26.1)
Legger til username-kolonne i auth_identities med UNIQUE constraint. Ved innlogging sender SvelteKit preferred_username fra Authentik til maskinrommet POST /auth/sync, som oppdaterer kolonnen. Grunnlaget for epost-ruting i fase 26: vegard@synops.no → username-oppslag.
This commit is contained in:
parent
6b81569581
commit
25fc1a1b59
4 changed files with 88 additions and 3 deletions
|
|
@ -31,6 +31,29 @@ async function fetchNodeId(accessToken: string): Promise<string | null> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync user profile (username) to maskinrommet on sign-in.
|
||||||
|
* Stores Authentik preferred_username in auth_identities.username.
|
||||||
|
*/
|
||||||
|
async function syncUsername(accessToken: string, username: string): Promise<void> {
|
||||||
|
const url = env.MASKINROMMET_URL ?? 'https://api.sidelinja.org';
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${url}/auth/sync`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${accessToken}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ username })
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error(`[auth] /auth/sync returned ${res.status}: ${await res.text()}`);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[auth] Failed to sync username to maskinrommet:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const { handle, signIn, signOut } = SvelteKitAuth({
|
export const { handle, signIn, signOut } = SvelteKitAuth({
|
||||||
trustHost: true,
|
trustHost: true,
|
||||||
providers: [
|
providers: [
|
||||||
|
|
@ -69,6 +92,11 @@ export const { handle, signIn, signOut } = SvelteKitAuth({
|
||||||
if (!token.node_id) {
|
if (!token.node_id) {
|
||||||
token.node_id = await fetchNodeId(account.access_token);
|
token.node_id = await fetchNodeId(account.access_token);
|
||||||
}
|
}
|
||||||
|
// Sync username from Authentik preferred_username on each sign-in
|
||||||
|
const p = profile as AuthentikProfile | undefined;
|
||||||
|
if (p?.preferred_username) {
|
||||||
|
await syncUsername(account.access_token, p.preferred_username);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return token;
|
return token;
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,7 @@ pub mod user_usage;
|
||||||
mod workspace;
|
mod workspace;
|
||||||
|
|
||||||
use axum::{extract::State, http::StatusCode, middleware, routing::{get, post}, Json, Router};
|
use axum::{extract::State, http::StatusCode, middleware, routing::{get, post}, Json, Router};
|
||||||
use serde::Serialize;
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::postgres::PgPoolOptions;
|
use sqlx::postgres::PgPoolOptions;
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt, EnvFilter};
|
||||||
|
|
@ -72,6 +72,16 @@ struct MeResponse {
|
||||||
authentik_sub: String,
|
authentik_sub: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct AuthSyncRequest {
|
||||||
|
username: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct AuthSyncResponse {
|
||||||
|
ok: bool,
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
// Strukturert logging: LOG_FORMAT=json for maskinlesbart, ellers human-readable.
|
// Strukturert logging: LOG_FORMAT=json for maskinlesbart, ellers human-readable.
|
||||||
|
|
@ -164,6 +174,7 @@ async fn main() {
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
.route("/health", get(health))
|
.route("/health", get(health))
|
||||||
.route("/me", get(me))
|
.route("/me", get(me))
|
||||||
|
.route("/auth/sync", post(auth_sync))
|
||||||
// WebSocket-endepunkt for sanntid via PG LISTEN/NOTIFY (oppgave 22.1)
|
// WebSocket-endepunkt for sanntid via PG LISTEN/NOTIFY (oppgave 22.1)
|
||||||
.route("/ws", get(ws::ws_handler))
|
.route("/ws", get(ws::ws_handler))
|
||||||
.route("/intentions/create_node", post(intentions::create_node))
|
.route("/intentions/create_node", post(intentions::create_node))
|
||||||
|
|
@ -304,3 +315,39 @@ async fn me(user: AuthUser) -> Json<MeResponse> {
|
||||||
authentik_sub: user.authentik_sub,
|
authentik_sub: user.authentik_sub,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// POST /auth/sync — synkroniser brukerprofil fra Authentik ved login.
|
||||||
|
/// Kalles av SvelteKit auth-callback etter vellykket innlogging.
|
||||||
|
/// Oppdaterer username i auth_identities fra Authentik preferred_username.
|
||||||
|
async fn auth_sync(
|
||||||
|
user: AuthUser,
|
||||||
|
State(state): State<AppState>,
|
||||||
|
Json(body): Json<AuthSyncRequest>,
|
||||||
|
) -> Result<Json<AuthSyncResponse>, StatusCode> {
|
||||||
|
let username = body.username.trim().to_lowercase();
|
||||||
|
|
||||||
|
if username.is_empty() {
|
||||||
|
tracing::warn!("auth_sync: tomt brukernavn for node_id={}", user.node_id);
|
||||||
|
return Err(StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlx::query(
|
||||||
|
"UPDATE auth_identities SET username = $1 WHERE node_id = $2",
|
||||||
|
)
|
||||||
|
.bind(&username)
|
||||||
|
.bind(user.node_id)
|
||||||
|
.execute(&state.db)
|
||||||
|
.await
|
||||||
|
.map_err(|e| {
|
||||||
|
tracing::error!("auth_sync: kunne ikke oppdatere username: {e}");
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR
|
||||||
|
})?;
|
||||||
|
|
||||||
|
tracing::info!(
|
||||||
|
"auth_sync: username='{}' for node_id={}",
|
||||||
|
username,
|
||||||
|
user.node_id
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(Json(AuthSyncResponse { ok: true }))
|
||||||
|
}
|
||||||
|
|
|
||||||
11
migrations/027_auth_username.sql
Normal file
11
migrations/027_auth_username.sql
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
-- 027: Legg til username-kolonne i auth_identities
|
||||||
|
-- Populeres fra Authentik preferred_username ved login.
|
||||||
|
-- Brukes til epost-ruting (fase 26): vegard@synops.no → auth_identities.username = 'vegard'
|
||||||
|
|
||||||
|
ALTER TABLE auth_identities
|
||||||
|
ADD COLUMN username TEXT UNIQUE;
|
||||||
|
|
||||||
|
-- Sett Vegards username fra seed-data
|
||||||
|
UPDATE auth_identities
|
||||||
|
SET username = 'vegard'
|
||||||
|
WHERE email = 'vnotnes@pm.me';
|
||||||
3
tasks.md
3
tasks.md
|
|
@ -345,8 +345,7 @@ Vaktmesteren kan sende epost (msmtp) og motta epost (Postfix → synops-mail).
|
||||||
Brukernavn@domene ruter til brukerens innboks. Alle domener (synops.no,
|
Brukernavn@domene ruter til brukerens innboks. Alle domener (synops.no,
|
||||||
sidelinja.org, vegard.info) ruter til samme bruker basert på username.
|
sidelinja.org, vegard.info) ruter til samme bruker basert på username.
|
||||||
|
|
||||||
- [~] 26.1 Username i auth_identities: legg til `username`-kolonne, populer fra Authentik `preferred_username` ved login. Unik constraint. Oppdater auth-callback i SvelteKit til å lagre username.
|
- [x] 26.1 Username i auth_identities: legg til `username`-kolonne, populer fra Authentik `preferred_username` ved login. Unik constraint. Oppdater auth-callback i SvelteKit til å lagre username.
|
||||||
> Påbegynt: 2026-03-18T19:00
|
|
||||||
- [ ] 26.2 msmtp oppsett: konfigurer utgående epost via SMTP-relay. Avsender: `vaktmester@synops.no`. Tilgjengelig som `synops-mail --send --to <epost> --subject <emne>` CLI-verktøy.
|
- [ ] 26.2 msmtp oppsett: konfigurer utgående epost via SMTP-relay. Avsender: `vaktmester@synops.no`. Tilgjengelig som `synops-mail --send --to <epost> --subject <emne>` CLI-verktøy.
|
||||||
- [ ] 26.3 MX-records: sett opp MX for synops.no, sidelinja.org, vegard.info som peker til serveren.
|
- [ ] 26.3 MX-records: sett opp MX for synops.no, sidelinja.org, vegard.info som peker til serveren.
|
||||||
- [ ] 26.4 Postfix minimal: installer Postfix som lokal MTA kun for mottak. Ingen relay, ingen kø for utgående. Pipe innkommende epost til `synops-mail --receive`.
|
- [ ] 26.4 Postfix minimal: installer Postfix som lokal MTA kun for mottak. Ingen relay, ingen kø for utgående. Pipe innkommende epost til `synops-mail --receive`.
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue