implement external authentication

This commit is contained in:
Aleksandr 2025-09-26 20:37:09 +03:00
parent fe04530f84
commit 6e4aed3ce9
10 changed files with 160 additions and 13 deletions

View file

@ -2,6 +2,9 @@ use eva::data;
use crate::types::user;
#[data(error, display("there is no such auth session"))]
pub struct NoSuchAuthSession;
#[data(copy, display(name))]
pub enum WhatTaken {
Nickname,

View file

@ -1,6 +1,6 @@
///! # Users functionality
///!
///! Subject for actions.
///! Subject of actions.
use eva::{data, time::Timestamp};
use crate::{
@ -8,6 +8,44 @@ use crate::{
types::{True, file, patch::Patch, session, user},
};
pub mod begin_auth {
use super::*;
#[data]
pub struct Args {
pub method: user::AuthoredAuth,
}
#[data]
pub struct Ok {
pub auth_session: user::AuthSessionId,
}
#[data(error, display("_"))]
pub enum Err {}
}
pub mod finish_auth {
use super::*;
#[data]
pub struct Args {
pub auth_session: user::AuthSessionId,
}
#[data]
pub struct Ok {
pub session: session::Token,
pub method: user::AuthoredAuth,
}
#[data(error)]
pub enum Err {
#[display("{_0}")]
NoSuchAuthSession(#[from] errors::NoSuchAuthSession),
}
}
pub mod check_auth {
use super::*;

View file

@ -5,5 +5,6 @@ use crate::{service::AuxFut, types::session};
#[auto_impl(&mut)]
pub trait AuthenticationMut: Send + Sync {
fn authenticate(&mut self, session: session::Token) -> impl AuxFut<()>;
fn clear(&mut self);
}

View file

@ -1,7 +1,9 @@
use eva::auto_impl;
use crate::{
requests::users::{check_auth, confirm_sign_up, get, sign_in, sign_up, update},
requests::users::{
begin_auth, check_auth, confirm_sign_up, finish_auth, get, sign_in, sign_up, update,
},
service::CallStep,
};
@ -15,6 +17,13 @@ pub trait UsersRef: Send + Sync {
#[auto_impl(&mut)]
pub trait UsersMut: UsersRef {
fn begin_auth(
&mut self,
) -> impl CallStep<begin_auth::Args, Ok = begin_auth::Ok, Err = begin_auth::Err>;
fn finish_auth(
&mut self,
) -> impl CallStep<finish_auth::Args, Ok = finish_auth::Ok, Err = finish_auth::Err>;
fn sign_in(&mut self) -> impl CallStep<sign_in::Args, Ok = sign_in::Ok, Err = sign_in::Err>;
fn sign_up(&mut self) -> impl CallStep<sign_up::Args, Ok = sign_up::Ok, Err = sign_up::Err>;

View file

@ -84,6 +84,7 @@ pub enum Kind {
Session = 0,
File,
Upload,
AuthSession,
User,
Author,
Game,

View file

@ -1,4 +1,4 @@
use std::borrow::Cow;
use std::{borrow::Cow, num::NonZeroI64};
use serde::{Serialize, de};
@ -10,6 +10,37 @@ use eva::{
use crate::types::{entity, file, slug::Slug};
entity::define_eid! {
pub struct AuthSessionId(AuthSession);
}
#[str(newtype, copy)]
pub struct TelegramUsername(Slug<23>);
#[data(copy, ord, display("tg:{_0}"))]
pub struct TelegramId(pub NonZeroI64);
#[data]
pub struct TelegramAuth {
pub bot_username: TelegramUsername,
}
#[data]
pub enum AuthoredAuth {
Telegram(#[from] TelegramAuth),
}
#[data]
pub enum DirectAuth {
Password,
}
#[data]
pub enum AuthMethod {
Authored(#[from] AuthoredAuth),
Direct(DirectAuth),
}
/// Miniature for the user.
#[data]
pub struct Mini {

View file

@ -2,7 +2,9 @@ use super::*;
use eva::str::format_compact;
use viendesu_core::requests::users::{check_auth, confirm_sign_up, get, sign_in, sign_up, update};
use viendesu_core::requests::users::{
begin_auth, check_auth, confirm_sign_up, finish_auth, get, sign_in, sign_up, update,
};
use crate::requests::users as requests;
@ -24,6 +26,25 @@ impl UsersRef for Hidden<'_> {
}
impl UsersMut for Hidden<'_> {
fn begin_auth(
&mut self,
) -> impl CallStep<begin_auth::Args, Ok = begin_auth::Ok, Err = begin_auth::Err> {
self.do_call(Method::POST, |begin_auth::Args { method }| {
("/users/begin-auth".into(), requests::BeginAuth { method })
})
}
fn finish_auth(
&mut self,
) -> impl CallStep<finish_auth::Args, Ok = finish_auth::Ok, Err = finish_auth::Err> {
self.do_call(Method::POST, |finish_auth::Args { auth_session }| {
(
format_compact!("/users/finish-auth/{auth_session}"),
requests::FinishAuth {},
)
})
}
fn confirm_sign_up(
&mut self,
) -> impl CallStep<confirm_sign_up::Args, Ok = confirm_sign_up::Ok, Err = confirm_sign_up::Err>

View file

@ -8,6 +8,22 @@ use viendesu_core::{
use super::status_code;
#[data]
pub struct BeginAuth {
pub method: core::AuthoredAuth,
}
impl_req!(BeginAuth => [reqs::begin_auth::Ok; reqs::begin_auth::Err]);
status_code::direct!(reqs::begin_auth::Ok => OK);
status_code::map!(reqs::begin_auth::Err => []);
#[data]
pub struct FinishAuth {}
impl_req!(FinishAuth => [reqs::finish_auth::Ok; reqs::finish_auth::Err]);
status_code::direct!(reqs::finish_auth::Ok => OK);
status_code::map!(reqs::finish_auth::Err => [NoSuchAuthSession]);
#[data]
pub struct CheckAuth {}
@ -99,4 +115,5 @@ const _: () = {
direct!(AlreadyTaken => BAD_REQUEST);
direct!(NotFoundOrCompleted => BAD_REQUEST);
direct!(InvalidSignUpToken => BAD_REQUEST);
direct!(NoSuchAuthSession => NOT_FOUND);
};

View file

@ -21,14 +21,13 @@ pub trait Types: Send + Sync + 'static {
type Service: Service + Clone;
}
pub async fn serve(
rx: SlaveRx,
config: config::Config,
router: axum::Router,
) -> eyre::Result<()> {
pub async fn serve(rx: SlaveRx, config: config::Config, router: axum::Router) -> eyre::Result<()> {
// TODO: use it.
_ = rx;
let config::Config { unencrypted, ssl: _ } = config;
let config::Config {
unencrypted,
ssl: _,
} = config;
let unencrypted = unencrypted.expect("SSL-only currently is not supported");
if !unencrypted.enable {

View file

@ -27,9 +27,11 @@ pub fn make<T: Types>(router: RouterScope<T>) -> RouterScope<T> {
// == Routes ==
fn users<T: Types>(router: RouterScope<T>) -> RouterScope<T> {
use crate::requests::users::{CheckAuth, ConfirmSignUp, Get, SignIn, SignUp, Update};
use crate::requests::users::{
BeginAuth, CheckAuth, ConfirmSignUp, FinishAuth, Get, SignIn, SignUp, Update,
};
use viendesu_core::requests::users::{
check_auth, confirm_sign_up, get, sign_in, sign_up, update,
begin_auth, check_auth, confirm_sign_up, finish_auth, get, sign_in, sign_up, update,
};
fn convert_update(u: Update) -> update::Update {
@ -59,7 +61,32 @@ fn users<T: Types>(router: RouterScope<T>) -> RouterScope<T> {
}),
)
.route(
"/check_auth",
"/begin-auth",
post(async |mut session: SessionOf<T>, ctx: Ctx<BeginAuth>| {
session
.users_mut()
.begin_auth()
.call(begin_auth::Args {
method: ctx.request.method,
})
.await
}),
)
.route(
"/finish-auth/{authsessid}",
post(
async |mut session: SessionOf<T>, mut ctx: Ctx<FinishAuth>| {
let auth_session: user::AuthSessionId = ctx.path().await?;
session
.users_mut()
.finish_auth()
.call(finish_auth::Args { auth_session })
.await
},
),
)
.route(
"/check-auth",
get(async |mut session: SessionOf<T>, _ctx: Ctx<CheckAuth>| {
session.users().check_auth().call(check_auth::Args {}).await
}),