feat: login UI adapter for oauth domain
This commit is contained in:
parent
2cf8145e31
commit
5637eb8415
1
src/forge/auth/adapter/input/mod.rs
Normal file
1
src/forge/auth/adapter/input/mod.rs
Normal file
|
@ -0,0 +1 @@
|
|||
pub mod web;
|
60
src/forge/auth/adapter/input/web/login.rs
Normal file
60
src/forge/auth/adapter/input/web/login.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use actix_web::{get, http::header, post, web, HttpResponse};
|
||||
|
||||
use crate::forge::auth::adapter::out::forge::SupportedForges;
|
||||
use crate::forge::auth::application::port::input::ui::{
|
||||
errors::*, login::RequestAuthorizationInterface,
|
||||
};
|
||||
use crate::forge::auth::application::services::request_authorization::command::RequestAuthorizationCommand;
|
||||
|
||||
use super::{templates::login::LoginCtxFactory, ServiceFactory, WebCtx};
|
||||
use crate::ActixCtx;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl RequestAuthorizationInterface for WebCtx {
|
||||
#[tracing::instrument(name = "web adapter request_oauth_authorization", skip(self))]
|
||||
async fn request_oauth_authorization(&self, forge_name: String) -> InUIResult<HttpResponse> {
|
||||
let service = ServiceFactory::request_authorization(
|
||||
SupportedForges::from_str(&forge_name).map_err(|_| InUIError::BadRequest)?,
|
||||
self,
|
||||
)?;
|
||||
|
||||
log::info!("service found");
|
||||
|
||||
let cmd = RequestAuthorizationCommand::new_command(forge_name)?;
|
||||
let auth_page = service.request_authorization(cmd).await?;
|
||||
Ok(HttpResponse::Found()
|
||||
.insert_header((header::LOCATION, auth_page.as_str()))
|
||||
.finish())
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/login")]
|
||||
async fn login_page(ctx: ActixCtx) -> InUIResult<HttpResponse> {
|
||||
let template_ctx =
|
||||
LoginCtxFactory::get_ctx(ctx.adapters.forges.get_supported_forges(), &ctx.routes);
|
||||
let page = ctx
|
||||
.templates
|
||||
.login_page
|
||||
.get_login_page(template_ctx)
|
||||
.unwrap();
|
||||
Ok(HttpResponse::Ok()
|
||||
.append_header((header::CONTENT_TYPE, "text/html; charset=UTF-8"))
|
||||
.body(page))
|
||||
}
|
||||
|
||||
#[post("/oauth/{forge}/login")]
|
||||
#[tracing::instrument(name = "web handler request_oauth_authorization", skip(ctx))]
|
||||
async fn request_oauth_authorization(
|
||||
ctx: ActixCtx,
|
||||
forge_name: web::Path<String>,
|
||||
) -> InUIResult<HttpResponse> {
|
||||
ctx.request_oauth_authorization(forge_name.into_inner())
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(login_page);
|
||||
cfg.service(request_oauth_authorization);
|
||||
}
|
81
src/forge/auth/adapter/input/web/mod.rs
Normal file
81
src/forge/auth/adapter/input/web/mod.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use actix_web::web::{self, Data};
|
||||
use url::Url;
|
||||
|
||||
use crate::forge::auth::adapter::out::db::DBAdapter;
|
||||
use crate::forge::auth::adapter::out::forge::{ForgeRepository, SupportedForges};
|
||||
use crate::forge::auth::application::port::input::ui::errors::{InUIError, InUIResult};
|
||||
use crate::forge::auth::application::services::request_authorization::service::RequestAuthorizationService;
|
||||
use crate::forge::auth::application::services::request_authorization::RequestAuthorizationUserCase;
|
||||
use crate::settings::Settings;
|
||||
|
||||
pub mod login;
|
||||
mod routes;
|
||||
mod templates;
|
||||
|
||||
use routes::RoutesRepository;
|
||||
|
||||
pub type ArcCtx = Arc<WebCtx>;
|
||||
pub type ActixCtx = Data<ArcCtx>;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WebCtx {
|
||||
pub routes: Arc<RoutesRepository>,
|
||||
pub adapters: Adapters,
|
||||
pub templates: templates::Templates,
|
||||
pub settings: Settings,
|
||||
}
|
||||
|
||||
impl WebCtx {
|
||||
pub fn new_actix_ctx(forges: ForgeRepository, db: DBAdapter, settings: Settings) -> ActixCtx {
|
||||
let routes = Arc::new(RoutesRepository::default());
|
||||
Data::new(Arc::new(Self {
|
||||
routes: routes.clone(),
|
||||
adapters: Adapters::new(forges, db),
|
||||
templates: templates::Templates::default(),
|
||||
settings,
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Adapters {
|
||||
pub forges: ForgeRepository,
|
||||
pub db: DBAdapter,
|
||||
}
|
||||
|
||||
impl Adapters {
|
||||
pub fn new(forges: ForgeRepository, db: DBAdapter) -> Self {
|
||||
Self { forges, db }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ServiceFactory;
|
||||
|
||||
impl ServiceFactory {
|
||||
pub fn request_authorization(
|
||||
forge_name: SupportedForges,
|
||||
ctx: &WebCtx,
|
||||
) -> InUIResult<Arc<dyn RequestAuthorizationUserCase>> {
|
||||
if let Some(forge) = ctx.adapters.forges.get_forge(&forge_name) {
|
||||
Ok(Arc::new(RequestAuthorizationService::new(
|
||||
ctx.adapters.db.save_oauth_state_adapter.clone(),
|
||||
forge.get_redirect_uri_adapter.clone(),
|
||||
Url::parse(&format!(
|
||||
"{}://{}{}",
|
||||
"http",
|
||||
&ctx.settings.server.domain,
|
||||
&ctx.routes.process_oauth_authorization_response(&forge_name)
|
||||
))
|
||||
.map_err(|_| InUIError::InternalServerError)?,
|
||||
)))
|
||||
} else {
|
||||
Err(InUIError::BadRequest)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.configure(login::services);
|
||||
}
|
28
src/forge/auth/adapter/input/web/routes.rs
Normal file
28
src/forge/auth/adapter/input/web/routes.rs
Normal file
|
@ -0,0 +1,28 @@
|
|||
use crate::forge::auth::adapter::out::forge::SupportedForges;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct RoutesRepository {
|
||||
process_oauth_authorization_response: String,
|
||||
pub login: String,
|
||||
oauth_login: String,
|
||||
}
|
||||
|
||||
impl Default for RoutesRepository {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
process_oauth_authorization_response: "/oauth/{forge}/authorize".to_string(),
|
||||
login: "/login".to_string(),
|
||||
oauth_login: "/oauth/{forge}/login".to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RoutesRepository {
|
||||
pub fn oauth_login(&self, forge_name: &SupportedForges) -> String {
|
||||
self.oauth_login.replace("{forge}", &forge_name.to_string())
|
||||
}
|
||||
pub fn process_oauth_authorization_response(&self, forge_name: &SupportedForges) -> String {
|
||||
self.process_oauth_authorization_response
|
||||
.replace("{forge}", &forge_name.to_string())
|
||||
}
|
||||
}
|
21
src/forge/auth/adapter/input/web/templates/login.html
Normal file
21
src/forge/auth/adapter/input/web/templates/login.html
Normal file
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
{% for forge in payload.forges -%}
|
||||
<form action="{{forge.path}}" method="POST">
|
||||
<button type="submit">
|
||||
{{forge.name}}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
{%- endfor %}
|
||||
|
||||
|
||||
</body>
|
||||
</html>
|
60
src/forge/auth/adapter/input/web/templates/login.rs
Normal file
60
src/forge/auth/adapter/input/web/templates/login.rs
Normal file
|
@ -0,0 +1,60 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::forge::auth::adapter::input::web::routes::RoutesRepository;
|
||||
use crate::forge::auth::adapter::out::forge::SupportedForges;
|
||||
|
||||
use super::{tera_context, TEMPLATES};
|
||||
|
||||
pub trait LoginPageInterface: Send + Sync {
|
||||
fn get_login_page(&self, ctx: LoginCtx) -> Result<String, Box<dyn std::error::Error>>;
|
||||
}
|
||||
|
||||
use super::TemplateFile;
|
||||
|
||||
pub const LOGIN_TEMPLATE: TemplateFile = TemplateFile::new("login", "login.html");
|
||||
pub fn register_templates(t: &mut tera::Tera) {
|
||||
LOGIN_TEMPLATE.register(t).expect(LOGIN_TEMPLATE.name);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Forge {
|
||||
name: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
impl Forge {
|
||||
pub fn new(name: String, path: String) -> Self {
|
||||
Self { name, path }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct LoginCtx {
|
||||
pub forges: Vec<Forge>,
|
||||
}
|
||||
|
||||
pub struct LoginCtxFactory;
|
||||
|
||||
impl LoginCtxFactory {
|
||||
pub fn get_ctx(supported_forges: Vec<SupportedForges>, routes: &RoutesRepository) -> LoginCtx {
|
||||
let mut forges = Vec::with_capacity(supported_forges.len());
|
||||
for s in supported_forges.iter() {
|
||||
forges.push(Forge::new(s.to_string(), routes.oauth_login(s)));
|
||||
}
|
||||
|
||||
LoginCtx { forges }
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct LoginPageTemplate;
|
||||
|
||||
impl LoginPageInterface for LoginPageTemplate {
|
||||
fn get_login_page(&self, ctx: LoginCtx) -> Result<String, Box<dyn std::error::Error>> {
|
||||
let ctx = tera_context(&ctx);
|
||||
let page = TEMPLATES
|
||||
.render(LOGIN_TEMPLATE.name, &ctx.borrow())
|
||||
.unwrap();
|
||||
Ok(page)
|
||||
}
|
||||
}
|
23
src/forge/auth/adapter/input/web/templates/mod.rs
Normal file
23
src/forge/auth/adapter/input/web/templates/mod.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
pub mod login;
|
||||
pub mod setup;
|
||||
mod utils;
|
||||
|
||||
use login::{LoginPageInterface, LoginPageTemplate};
|
||||
|
||||
pub use setup::{TemplateFile, PAYLOAD_KEY, TEMPLATES};
|
||||
pub use utils::tera_context;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Templates {
|
||||
pub login_page: Arc<dyn LoginPageInterface>,
|
||||
}
|
||||
|
||||
impl Default for Templates {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
login_page: Arc::new(LoginPageTemplate),
|
||||
}
|
||||
}
|
||||
}
|
62
src/forge/auth/adapter/input/web/templates/setup.rs
Normal file
62
src/forge/auth/adapter/input/web/templates/setup.rs
Normal file
|
@ -0,0 +1,62 @@
|
|||
use lazy_static::lazy_static;
|
||||
use rust_embed::RustEmbed;
|
||||
use tera::*;
|
||||
|
||||
pub struct TemplateFile {
|
||||
pub name: &'static str,
|
||||
pub path: &'static str,
|
||||
}
|
||||
|
||||
impl TemplateFile {
|
||||
pub const fn new(name: &'static str, path: &'static str) -> Self {
|
||||
Self { name, path }
|
||||
}
|
||||
|
||||
pub fn register(&self, t: &mut Tera) -> std::result::Result<(), tera::Error> {
|
||||
t.add_raw_template(self.name, &Templates::get_template(self).expect(self.name))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[allow(dead_code)]
|
||||
pub fn register_from_file(&self, t: &mut Tera) -> std::result::Result<(), tera::Error> {
|
||||
use std::path::Path;
|
||||
t.add_template_file(Path::new("templates/").join(self.path), Some(self.name))
|
||||
}
|
||||
}
|
||||
|
||||
pub const PAYLOAD_KEY: &str = "payload";
|
||||
|
||||
//pub const BASE: TemplateFile = TemplateFile::new("base", "components/base.html");
|
||||
//pub const FOOTER: TemplateFile = TemplateFile::new("footer", "components/footer.html");
|
||||
//pub const PUB_NAV: TemplateFile = TemplateFile::new("pub_nav", "components/nav/pub.html");
|
||||
//pub const AUTH_NAV: TemplateFile = TemplateFile::new("auth_nav", "components/nav/auth.html");
|
||||
|
||||
lazy_static! {
|
||||
pub static ref TEMPLATES: Tera = {
|
||||
let mut tera = Tera::default();
|
||||
// for t in [BASE, FOOTER, PUB_NAV, AUTH_NAV].iter() {
|
||||
// t.register(&mut tera).unwrap();
|
||||
// }
|
||||
// errors::register_templates(&mut tera);
|
||||
tera.autoescape_on(vec![".html", ".sql"]);
|
||||
super::login::register_templates(&mut tera);
|
||||
// auth::register_templates(&mut tera);
|
||||
// gists::register_templates(&mut tera);
|
||||
tera
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "src/forge/auth/adapter/input/web/templates/"]
|
||||
#[include = "*.html"]
|
||||
#[exclude = "*.rs"]
|
||||
struct Templates;
|
||||
|
||||
impl Templates {
|
||||
fn get_template(t: &TemplateFile) -> Option<String> {
|
||||
match Self::get(t.path) {
|
||||
Some(file) => Some(String::from_utf8_lossy(&file.data).into_owned()),
|
||||
None => None,
|
||||
}
|
||||
}
|
||||
}
|
11
src/forge/auth/adapter/input/web/templates/utils.rs
Normal file
11
src/forge/auth/adapter/input/web/templates/utils.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use std::cell::RefCell;
|
||||
|
||||
use super::PAYLOAD_KEY;
|
||||
use serde::Serialize;
|
||||
use tera::Context;
|
||||
|
||||
pub fn tera_context<T: Serialize>(s: &T) -> RefCell<Context> {
|
||||
let c = RefCell::new(Context::new());
|
||||
c.borrow_mut().insert(PAYLOAD_KEY, s);
|
||||
c
|
||||
}
|
Loading…
Reference in a new issue