État, auth & handlers
Le module ne valide pas les JWT lui-même. Le core authentifie la requête puis la proxifie en injectant l'identité dans des en-têtes ; un middleware les lit et les pose dans les extensions de la requête.
L'état #
// state.rs
#[derive(Clone)]
pub struct AppState {
pub db: sqlx::PgPool,
pub settings: std::sync::Arc<Settings>,
}Le middleware d'authentification #
// middleware.rs — lit l'identité injectée par le proxy du core
#[derive(Clone)]
pub struct MemoUser { pub id: Uuid, pub role: String, pub email: String }
pub async fn require_auth(mut req: Request, next: Next) -> Result<Response, MemoError> {
let h = req.headers();
let id = h.get("x-kubuno-user-id")
.and_then(|v| v.to_str().ok())
.and_then(|s| Uuid::parse_str(s).ok())
.ok_or(MemoError::Unauthorized)?;
let role = h.get("x-kubuno-user-role").and_then(|v| v.to_str().ok()).unwrap_or("user").to_string();
let email = h.get("x-kubuno-user-email").and_then(|v| v.to_str().ok()).unwrap_or("").to_string();
req.extensions_mut().insert(MemoUser { id, role, email });
Ok(next.run(req).await)
}Note
En-têtes injectés par le proxy : x-kubuno-user-id, x-kubuno-user-role, x-kubuno-user-email (et X-Internal-Secret à la place de l'Authorization).
Un handler #
// handlers/notes.rs
pub async fn list(
State(state): State<AppState>,
Extension(user): Extension<MemoUser>,
) -> Result<Json<serde_json::Value>> {
let notes = sqlx::query_as!(
Note,
"SELECT * FROM memo.notes WHERE owner_id = $1 ORDER BY pinned DESC, updated_at DESC",
user.id
)
.fetch_all(&state.db)
.await?;
Ok(Json(json!({ "notes": notes })))
}Les extracteurs Axum font le travail : State donne l'état, Extension récupère l'utilisateur posé par le middleware, ? propage l'erreur.
Lire l'utilisateur & répondre — dans d'autres langages #
Quel que soit le langage, on lit les mêmes en-têtes et on filtre par owner_id :
// Rust — Axum : l'identité est dans les en-têtes injectés par le proxy
async fn list(headers: HeaderMap, State(db): State<PgPool>) -> impl IntoResponse {
let user_id = headers.get("x-kubuno-user-id")
.and_then(|v| v.to_str().ok()).unwrap_or("");
let notes = sqlx::query_as::<_, Note>(
"SELECT * FROM memo.notes WHERE owner_id = $1::uuid")
.bind(user_id).fetch_all(&db).await.unwrap_or_default();
Json(json!({ "notes": notes }))
}<?php
// PHP : les en-têtes HTTP deviennent HTTP_X_KUBUNO_*
$userId = $_SERVER['HTTP_X_KUBUNO_USER_ID'] ?? '';
$role = $_SERVER['HTTP_X_KUBUNO_USER_ROLE'] ?? 'user';
$stmt = $pdo->prepare('SELECT * FROM memo.notes WHERE owner_id = :uid');
$stmt->execute([':uid' => $userId]);
header('Content-Type: application/json');
echo json_encode(['notes' => $stmt->fetchAll(PDO::FETCH_ASSOC)]);# Python — Flask
@app.get("/notes")
def list_notes():
user_id = request.headers.get("X-Kubuno-User-Id", "")
rows = db.execute(
"SELECT id, title FROM memo.notes WHERE owner_id = %s", (user_id,)
).fetchall()
return jsonify(notes=[dict(r) for r in rows])// Go — net/http
func list(w http.ResponseWriter, r *http.Request) {
userID := r.Header.Get("X-Kubuno-User-Id")
rows, _ := db.Query(`SELECT id, title FROM memo.notes WHERE owner_id = $1`, userID)
defer rows.Close()
notes := scanNotes(rows)
json.NewEncoder(w).Encode(map[string]any{"notes": notes})
}# Perl — Mojolicious::Lite
get '/notes' => sub ($c) {
my $user_id = $c->req->headers->header('X-Kubuno-User-Id') // '';
my $rows = $dbh->selectall_arrayref(
'SELECT id, title FROM memo.notes WHERE owner_id = ?',
{ Slice => {} }, $user_id);
$c->render(json => { notes => $rows });
};