diff --git a/geneit_app/.env b/geneit_app/.env new file mode 100644 index 0000000..b34ba09 --- /dev/null +++ b/geneit_app/.env @@ -0,0 +1 @@ +REACT_APP_BACKEND=http://localhost:8000 \ No newline at end of file diff --git a/geneit_app/src/App.test.tsx b/geneit_app/src/App.test.tsx deleted file mode 100644 index 2a68616..0000000 --- a/geneit_app/src/App.test.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/geneit_app/src/api/ApiClient.ts b/geneit_app/src/api/ApiClient.ts new file mode 100644 index 0000000..e5418b9 --- /dev/null +++ b/geneit_app/src/api/ApiClient.ts @@ -0,0 +1,52 @@ +interface APIResponse { + data: any; + status: number; +} + +export class ApiError extends Error { + constructor(message: string, public code: number, public data: any) { + super(message); + } +} + +export class APIClient { + /** + * Get backend URL + */ + static backendURL(): string { + const URL = process.env.REACT_APP_BACKEND ?? ""; + if (URL.length === 0) throw new Error("Backend URL undefined!"); + return URL; + } + + /** + * Perform a request on the backend + */ + static async exec(args: { + uri: string; + method: "GET" | "POST" | "DELETE"; + allowFail?: boolean; + jsonData?: any; + }): Promise { + const res = await fetch(this.backendURL() + args.uri, { + method: args.method, + body: args.jsonData ? JSON.stringify(args.jsonData) : undefined, + headers: { + "Content-Type": args.jsonData ? "application/json" : "text/plain", + }, + }); + + let data; + if (res.headers.get("content-type") === "application/json") + data = await res.json(); + else data = await res.blob(); + + if (!args.allowFail && !res.ok) + throw new ApiError("Request failed!", res.status, data); + + return { + data: data, + status: res.status, + }; + } +} diff --git a/geneit_app/src/api/ServerApi.ts b/geneit_app/src/api/ServerApi.ts new file mode 100644 index 0000000..b25fdc0 --- /dev/null +++ b/geneit_app/src/api/ServerApi.ts @@ -0,0 +1,47 @@ +import { APIClient } from "./ApiClient"; + +interface LenConstraint { + min: number; + max: number; +} + +interface Constraints { + mail_len: LenConstraint; + user_name_len: LenConstraint; + password_len: LenConstraint; +} + +interface OIDCProvider { + id: string; + name: string; +} + +export interface ServerConfig { + constraints: Constraints; + mail: string; + oidc_providers: OIDCProvider[]; +} + +let config: ServerConfig | null = null; + +export class ServerApi { + /** + * Get server configuration + */ + static async LoadConfig(): Promise { + config = ( + await APIClient.exec({ + uri: "/server/config", + method: "GET", + }) + ).data; + } + + /** + * Get cached configuration + */ + static Config(): ServerConfig { + if (config === null) throw new Error("Missing configuration!"); + return config; + } +} diff --git a/geneit_app/src/index.tsx b/geneit_app/src/index.tsx index 032464f..34f9358 100644 --- a/geneit_app/src/index.tsx +++ b/geneit_app/src/index.tsx @@ -1,17 +1,30 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import "./index.css"; +import App from "./App"; +import reportWebVitals from "./reportWebVitals"; +import { ServerApi } from "./api/ServerApi"; -const root = ReactDOM.createRoot( - document.getElementById('root') as HTMLElement -); -root.render( - - - -); +async function init() { + try { + await ServerApi.LoadConfig(); + + const root = ReactDOM.createRoot( + document.getElementById("root") as HTMLElement + ); + + root.render( + + + + ); + } catch (e) { + console.error(e); + alert("Echec de l'initialisation de l'application !"); + } +} + +init(); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) diff --git a/geneit_backend/Cargo.lock b/geneit_backend/Cargo.lock index bd1efac..2f0d5fa 100644 --- a/geneit_backend/Cargo.lock +++ b/geneit_backend/Cargo.lock @@ -19,6 +19,21 @@ dependencies = [ "tracing", ] +[[package]] +name = "actix-cors" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b340e9cfa5b08690aae90fb61beb44e9b06f44fe3d0f93781aaa58cfba86245e" +dependencies = [ + "actix-utils", + "actix-web", + "derive_more", + "futures-util", + "log", + "once_cell", + "smallvec", +] + [[package]] name = "actix-http" version = "3.3.1" @@ -779,6 +794,7 @@ dependencies = [ name = "geneit_backend" version = "0.1.0" dependencies = [ + "actix-cors", "actix-remote-ip", "actix-web", "anyhow", diff --git a/geneit_backend/Cargo.toml b/geneit_backend/Cargo.toml index 8554c2a..4f81b7f 100644 --- a/geneit_backend/Cargo.toml +++ b/geneit_backend/Cargo.toml @@ -12,6 +12,7 @@ clap = { version = "4.3.0", features = ["derive", "env"] } lazy_static = "1.4.0" anyhow = "1.0.71" actix-web = "4.3.1" +actix-cors = "0.6.4" futures-util = "0.3.28" diesel = { version = "2.0.4", features = ["postgres"] } serde = { version = "1.0.163", features = ["derive"] } diff --git a/geneit_backend/src/app_config.rs b/geneit_backend/src/app_config.rs index 49b1f90..fa9a0e9 100644 --- a/geneit_backend/src/app_config.rs +++ b/geneit_backend/src/app_config.rs @@ -81,19 +81,11 @@ pub struct AppConfig { pub smtp_password: Option, /// Password reset URL - #[clap( - long, - env, - default_value = "http://localhost:3000/reset_password#TOKEN" - )] + #[clap(long, env, default_value = "APP_ORIGIN/reset_password#TOKEN")] pub reset_password_url: String, /// Delete account URL - #[clap( - long, - env, - default_value = "http://localhost:3000/delete_account#TOKEN" - )] + #[clap(long, env, default_value = "APP_ORIGIN/delete_account#TOKEN")] pub delete_account_url: String, /// URL where the OpenID configuration can be found @@ -121,8 +113,8 @@ pub struct AppConfig { pub oidc_client_secret: String, /// OpenID login redirect URL - #[arg(long, env, default_value = "http://localhost:3000/oidc_cb")] - pub oidc_redirect_url: String, + #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")] + oidc_redirect_url: String, } lazy_static::lazy_static! { @@ -159,12 +151,16 @@ impl AppConfig { /// Get password reset URL pub fn get_password_reset_url(&self, token: &str) -> String { - self.reset_password_url.replace("TOKEN", token) + self.reset_password_url + .replace("APP_ORIGIN", &self.website_origin) + .replace("TOKEN", token) } /// Get account delete URL pub fn get_account_delete_url(&self, token: &str) -> String { - self.delete_account_url.replace("TOKEN", token) + self.delete_account_url + .replace("APP_ORIGIN", &self.website_origin) + .replace("TOKEN", token) } /// Get OpenID providers configuration @@ -181,6 +177,12 @@ impl AppConfig { name: self.oidc_provider_name.as_str(), }] } + + /// Get OIDC callback URL + pub fn oidc_redirect_url(&self) -> String { + self.oidc_redirect_url + .replace("APP_ORIGIN", &self.website_origin) + } } #[derive(Debug, Clone, serde::Serialize)] diff --git a/geneit_backend/src/main.rs b/geneit_backend/src/main.rs index 4587b5f..1d58677 100644 --- a/geneit_backend/src/main.rs +++ b/geneit_backend/src/main.rs @@ -1,3 +1,4 @@ +use actix_cors::Cors; use actix_remote_ip::RemoteIPConfig; use actix_web::middleware::Logger; use actix_web::{web, App, HttpServer}; @@ -12,6 +13,14 @@ async fn main() -> std::io::Result<()> { HttpServer::new(|| { App::new() + .wrap( + Cors::default() + .allowed_origin(&AppConfig::get().website_origin) + .allowed_methods(vec!["GET", "POST"]) + .allowed_header("X-Auth-Token") + .supports_credentials() + .max_age(3600), + ) .wrap(Logger::default()) .app_data(web::Data::new(RemoteIPConfig { proxy: AppConfig::get().proxy_ip.clone(), diff --git a/geneit_backend/src/services/openid_service.rs b/geneit_backend/src/services/openid_service.rs index 43607a5..db37c9d 100644 --- a/geneit_backend/src/services/openid_service.rs +++ b/geneit_backend/src/services/openid_service.rs @@ -96,7 +96,7 @@ pub async fn start_login(prov_id: &str, ip: IpAddr) -> anyhow::Result { Ok(prov.conf.gen_authorization_url( prov.prov.client_id, &state_key, - &AppConfig::get().oidc_redirect_url, + &AppConfig::get().oidc_redirect_url(), )) } @@ -133,7 +133,7 @@ pub async fn finish_login( prov.prov.client_id, prov.prov.client_secret, code, - &AppConfig::get().oidc_redirect_url, + &AppConfig::get().oidc_redirect_url(), ) .await .map_err(|e| OpenIDServiceError::QueryTokenEndpoint(e.to_string()))?;