auth works, getting email not yet

This commit is contained in:
Jacob Lifshay 2024-04-08 03:00:24 -07:00
parent 663af59488
commit 5eabc89280
Signed by: programmerjake
SSH key fingerprint: SHA256:B1iRVvUJkvd7upMIiMqn6OyxvD2SgJkAH3ZnUOj6z+c
5 changed files with 77 additions and 5 deletions

1
Cargo.lock generated
View file

@ -2287,6 +2287,7 @@ dependencies = [
"futures", "futures",
"indexmap 2.2.6", "indexmap 2.2.6",
"listenfd", "listenfd",
"log",
"openidconnect", "openidconnect",
"serde", "serde",
"serde_json", "serde_json",

View file

@ -15,6 +15,7 @@ eyre = "0.6.12"
futures = "0.3.30" futures = "0.3.30"
indexmap = { version = "2.2.6", features = ["serde"] } indexmap = { version = "2.2.6", features = ["serde"] }
listenfd = "1.0.1" listenfd = "1.0.1"
log = "0.4.21"
openidconnect = "3.5.0" openidconnect = "3.5.0"
serde = { version = "1.0.197", features = ["derive"] } serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.115" serde_json = "1.0.115"

View file

@ -3,6 +3,7 @@ pretty_name = "Google"
issuer_url = "https://accounts.google.com" issuer_url = "https://accounts.google.com"
client_id = "<TODO>" client_id = "<TODO>"
secret = "<TODO>" secret = "<TODO>"
redirect_url = "https://my-site/subscription/callback/google"
scopes = ["email"] scopes = ["email"]
[oidc.debian-salsa] [oidc.debian-salsa]
@ -10,4 +11,5 @@ pretty_name = "Debian Salsa"
issuer_url = "https://salsa.debian.org" issuer_url = "https://salsa.debian.org"
client_id = "<TODO>" client_id = "<TODO>"
secret = "<TODO>" secret = "<TODO>"
redirect_url = "https://my-site/subscription/callback/debian-salsa"
scopes = ["email"] scopes = ["email"]

View file

@ -5,16 +5,18 @@ use actix_web::{
get, get,
http::{ http::{
header::{ContentType, LOCATION}, header::{ContentType, LOCATION},
uri::PathAndQuery,
StatusCode, Uri, StatusCode, Uri,
}, },
web::{self, ServiceConfig}, web::{self, ServiceConfig},
HttpResponse, HttpResponseBuilder, Responder, HttpResponse, HttpResponseBuilder, Responder,
}; };
use openidconnect::{ use openidconnect::{
core::CoreAuthenticationFlow, http::HeaderValue, CsrfToken, EndUserEmail, Nonce, core::CoreAuthenticationFlow, http::HeaderValue, reqwest::async_http_client, url::Url,
AuthorizationCode, CsrfToken, EndUserEmail, Nonce, RedirectUrl, TokenResponse,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{fmt::Write, marker::PhantomData}; use std::{borrow::Cow, fmt::Write, marker::PhantomData};
use tinytemplate::TinyTemplate; use tinytemplate::TinyTemplate;
pub trait DynTemplate { pub trait DynTemplate {
@ -125,12 +127,76 @@ impl OIDCProvider {}
const NONCE: &'static str = "nonce"; const NONCE: &'static str = "nonce";
const CSRF_TOKEN: &'static str = "csrf_token"; const CSRF_TOKEN: &'static str = "csrf_token";
#[derive(Deserialize)]
struct AuthQuery {
code: AuthorizationCode,
state: CsrfToken,
}
#[get("/subscription/callback/{provider}")]
pub async fn callback(
config: web::Data<Config>,
session: Session,
path: web::Path<(String,)>,
query: web::Query<AuthQuery>,
) -> impl Responder {
let mut resp = HttpResponse::SeeOther().body("");
resp.headers_mut()
.insert(LOCATION, "/subscription".parse().unwrap());
if SessionState::get(&session).is_some() {
return resp;
}
let Ok(Some(csrf_token)) = session.get::<CsrfToken>(CSRF_TOKEN) else {
return resp;
};
let Ok(Some(nonce)) = session.get::<Nonce>(NONCE) else {
return resp;
};
session.remove(CSRF_TOKEN);
session.remove(NONCE);
if csrf_token.secret() != query.state.secret() {
return resp;
}
let Some(provider) = config.oidc.get(&path.0) else {
return resp;
};
let token = match provider
.state()
.client
.exchange_code(query.into_inner().code)
.request_async(async_http_client)
.await
{
Ok(token) => token,
Err(e) => {
log::warn!("Error getting token: {e}");
return resp;
}
};
dbg!(&token);
let claims = match token
.id_token()
.unwrap()
.claims(&provider.state().client.id_token_verifier(), &nonce)
{
Ok(claims) => claims,
Err(e) => {
log::warn!("Error verifying token: {e}");
return resp;
}
};
let Some(email) = claims.email().cloned() else {
return resp;
};
SessionState { email }.set(&session);
resp
}
#[get("/subscription/login/{provider}")] #[get("/subscription/login/{provider}")]
pub async fn login( pub async fn login(
config: web::Data<Config>, config: web::Data<Config>,
session: Session, session: Session,
path: web::Path<(String,)>, path: web::Path<(String,)>,
uri: Uri,
) -> impl Responder { ) -> impl Responder {
let mut resp = HttpResponse::SeeOther().body(""); let mut resp = HttpResponse::SeeOther().body("");
resp.headers_mut() resp.headers_mut()
@ -179,7 +245,7 @@ pub async fn subscription(config: web::Data<Config>, session: Session) -> impl R
} }
pub fn all_services(cfg: &mut ServiceConfig) { pub fn all_services(cfg: &mut ServiceConfig) {
cfg.service(subscription).service(login); cfg.service(subscription).service(login).service(callback);
} }
#[cfg(test)] #[cfg(test)]

View file

@ -5,7 +5,7 @@ use futures::future::try_join_all;
use indexmap::IndexMap; use indexmap::IndexMap;
use openidconnect::{ use openidconnect::{
core::{CoreClient, CoreProviderMetadata}, core::{CoreClient, CoreProviderMetadata},
ClientId, ClientSecret, IssuerUrl, Scope, ClientId, ClientSecret, IssuerUrl, RedirectUrl, Scope,
}; };
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -55,6 +55,7 @@ pub struct OIDCProvider {
pub issuer_url: IssuerUrl, pub issuer_url: IssuerUrl,
pub client_id: ClientId, pub client_id: ClientId,
pub secret: ClientSecret, pub secret: ClientSecret,
pub redirect_url: RedirectUrl,
pub scopes: Vec<Scope>, pub scopes: Vec<Scope>,
#[serde(skip)] #[serde(skip)]
state: Option<OIDCProviderState>, state: Option<OIDCProviderState>,
@ -76,6 +77,7 @@ impl OIDCProvider {
Some(self.secret.clone()), Some(self.secret.clone()),
); );
let client = client.disable_openid_scope(); let client = client.disable_openid_scope();
let client = client.set_redirect_uri(self.redirect_url.clone());
self.state = Some(OIDCProviderState { self.state = Some(OIDCProviderState {
client, client,
provider_metadata, provider_metadata,