Load server config on react app
This commit is contained in:
		
							
								
								
									
										1
									
								
								geneit_app/.env
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								geneit_app/.env
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					REACT_APP_BACKEND=http://localhost:8000
 | 
				
			||||||
@@ -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(<App />);
 | 
					 | 
				
			||||||
  const linkElement = screen.getByText(/learn react/i);
 | 
					 | 
				
			||||||
  expect(linkElement).toBeInTheDocument();
 | 
					 | 
				
			||||||
});
 | 
					 | 
				
			||||||
							
								
								
									
										52
									
								
								geneit_app/src/api/ApiClient.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								geneit_app/src/api/ApiClient.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<APIResponse> {
 | 
				
			||||||
 | 
					    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,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										47
									
								
								geneit_app/src/api/ServerApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								geneit_app/src/api/ServerApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<void> {
 | 
				
			||||||
 | 
					    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;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,17 +1,30 @@
 | 
				
			|||||||
import React from 'react';
 | 
					import React from "react";
 | 
				
			||||||
import ReactDOM from 'react-dom/client';
 | 
					import ReactDOM from "react-dom/client";
 | 
				
			||||||
import './index.css';
 | 
					import "./index.css";
 | 
				
			||||||
import App from './App';
 | 
					import App from "./App";
 | 
				
			||||||
import reportWebVitals from './reportWebVitals';
 | 
					import reportWebVitals from "./reportWebVitals";
 | 
				
			||||||
 | 
					import { ServerApi } from "./api/ServerApi";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const root = ReactDOM.createRoot(
 | 
					async function init() {
 | 
				
			||||||
  document.getElementById('root') as HTMLElement
 | 
					  try {
 | 
				
			||||||
);
 | 
					    await ServerApi.LoadConfig();
 | 
				
			||||||
root.render(
 | 
					
 | 
				
			||||||
  <React.StrictMode>
 | 
					    const root = ReactDOM.createRoot(
 | 
				
			||||||
    <App />
 | 
					      document.getElementById("root") as HTMLElement
 | 
				
			||||||
  </React.StrictMode>
 | 
					    );
 | 
				
			||||||
);
 | 
					
 | 
				
			||||||
 | 
					    root.render(
 | 
				
			||||||
 | 
					      <React.StrictMode>
 | 
				
			||||||
 | 
					        <App />
 | 
				
			||||||
 | 
					      </React.StrictMode>
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  } 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
 | 
					// If you want to start measuring performance in your app, pass a function
 | 
				
			||||||
// to log results (for example: reportWebVitals(console.log))
 | 
					// to log results (for example: reportWebVitals(console.log))
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								geneit_backend/Cargo.lock
									
									
									
										generated
									
									
									
								
							@@ -19,6 +19,21 @@ dependencies = [
 | 
				
			|||||||
 "tracing",
 | 
					 "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]]
 | 
					[[package]]
 | 
				
			||||||
name = "actix-http"
 | 
					name = "actix-http"
 | 
				
			||||||
version = "3.3.1"
 | 
					version = "3.3.1"
 | 
				
			||||||
@@ -779,6 +794,7 @@ dependencies = [
 | 
				
			|||||||
name = "geneit_backend"
 | 
					name = "geneit_backend"
 | 
				
			||||||
version = "0.1.0"
 | 
					version = "0.1.0"
 | 
				
			||||||
dependencies = [
 | 
					dependencies = [
 | 
				
			||||||
 | 
					 "actix-cors",
 | 
				
			||||||
 "actix-remote-ip",
 | 
					 "actix-remote-ip",
 | 
				
			||||||
 "actix-web",
 | 
					 "actix-web",
 | 
				
			||||||
 "anyhow",
 | 
					 "anyhow",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ clap = { version = "4.3.0", features = ["derive", "env"] }
 | 
				
			|||||||
lazy_static = "1.4.0"
 | 
					lazy_static = "1.4.0"
 | 
				
			||||||
anyhow = "1.0.71"
 | 
					anyhow = "1.0.71"
 | 
				
			||||||
actix-web = "4.3.1"
 | 
					actix-web = "4.3.1"
 | 
				
			||||||
 | 
					actix-cors = "0.6.4"
 | 
				
			||||||
futures-util = "0.3.28"
 | 
					futures-util = "0.3.28"
 | 
				
			||||||
diesel = { version = "2.0.4", features = ["postgres"] }
 | 
					diesel = { version = "2.0.4", features = ["postgres"] }
 | 
				
			||||||
serde = { version = "1.0.163", features = ["derive"] }
 | 
					serde = { version = "1.0.163", features = ["derive"] }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -81,19 +81,11 @@ pub struct AppConfig {
 | 
				
			|||||||
    pub smtp_password: Option<String>,
 | 
					    pub smtp_password: Option<String>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Password reset URL
 | 
					    /// Password reset URL
 | 
				
			||||||
    #[clap(
 | 
					    #[clap(long, env, default_value = "APP_ORIGIN/reset_password#TOKEN")]
 | 
				
			||||||
        long,
 | 
					 | 
				
			||||||
        env,
 | 
					 | 
				
			||||||
        default_value = "http://localhost:3000/reset_password#TOKEN"
 | 
					 | 
				
			||||||
    )]
 | 
					 | 
				
			||||||
    pub reset_password_url: String,
 | 
					    pub reset_password_url: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// Delete account URL
 | 
					    /// Delete account URL
 | 
				
			||||||
    #[clap(
 | 
					    #[clap(long, env, default_value = "APP_ORIGIN/delete_account#TOKEN")]
 | 
				
			||||||
        long,
 | 
					 | 
				
			||||||
        env,
 | 
					 | 
				
			||||||
        default_value = "http://localhost:3000/delete_account#TOKEN"
 | 
					 | 
				
			||||||
    )]
 | 
					 | 
				
			||||||
    pub delete_account_url: String,
 | 
					    pub delete_account_url: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// URL where the OpenID configuration can be found
 | 
					    /// URL where the OpenID configuration can be found
 | 
				
			||||||
@@ -121,8 +113,8 @@ pub struct AppConfig {
 | 
				
			|||||||
    pub oidc_client_secret: String,
 | 
					    pub oidc_client_secret: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// OpenID login redirect URL
 | 
					    /// OpenID login redirect URL
 | 
				
			||||||
    #[arg(long, env, default_value = "http://localhost:3000/oidc_cb")]
 | 
					    #[arg(long, env, default_value = "APP_ORIGIN/oidc_cb")]
 | 
				
			||||||
    pub oidc_redirect_url: String,
 | 
					    oidc_redirect_url: String,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
lazy_static::lazy_static! {
 | 
					lazy_static::lazy_static! {
 | 
				
			||||||
@@ -159,12 +151,16 @@ impl AppConfig {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    /// Get password reset URL
 | 
					    /// Get password reset URL
 | 
				
			||||||
    pub fn get_password_reset_url(&self, token: &str) -> String {
 | 
					    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
 | 
					    /// Get account delete URL
 | 
				
			||||||
    pub fn get_account_delete_url(&self, token: &str) -> String {
 | 
					    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
 | 
					    /// Get OpenID providers configuration
 | 
				
			||||||
@@ -181,6 +177,12 @@ impl AppConfig {
 | 
				
			|||||||
            name: self.oidc_provider_name.as_str(),
 | 
					            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)]
 | 
					#[derive(Debug, Clone, serde::Serialize)]
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					use actix_cors::Cors;
 | 
				
			||||||
use actix_remote_ip::RemoteIPConfig;
 | 
					use actix_remote_ip::RemoteIPConfig;
 | 
				
			||||||
use actix_web::middleware::Logger;
 | 
					use actix_web::middleware::Logger;
 | 
				
			||||||
use actix_web::{web, App, HttpServer};
 | 
					use actix_web::{web, App, HttpServer};
 | 
				
			||||||
@@ -12,6 +13,14 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    HttpServer::new(|| {
 | 
					    HttpServer::new(|| {
 | 
				
			||||||
        App::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())
 | 
					            .wrap(Logger::default())
 | 
				
			||||||
            .app_data(web::Data::new(RemoteIPConfig {
 | 
					            .app_data(web::Data::new(RemoteIPConfig {
 | 
				
			||||||
                proxy: AppConfig::get().proxy_ip.clone(),
 | 
					                proxy: AppConfig::get().proxy_ip.clone(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -96,7 +96,7 @@ pub async fn start_login(prov_id: &str, ip: IpAddr) -> anyhow::Result<String> {
 | 
				
			|||||||
    Ok(prov.conf.gen_authorization_url(
 | 
					    Ok(prov.conf.gen_authorization_url(
 | 
				
			||||||
        prov.prov.client_id,
 | 
					        prov.prov.client_id,
 | 
				
			||||||
        &state_key,
 | 
					        &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_id,
 | 
				
			||||||
            prov.prov.client_secret,
 | 
					            prov.prov.client_secret,
 | 
				
			||||||
            code,
 | 
					            code,
 | 
				
			||||||
            &AppConfig::get().oidc_redirect_url,
 | 
					            &AppConfig::get().oidc_redirect_url(),
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
        .await
 | 
					        .await
 | 
				
			||||||
        .map_err(|e| OpenIDServiceError::QueryTokenEndpoint(e.to_string()))?;
 | 
					        .map_err(|e| OpenIDServiceError::QueryTokenEndpoint(e.to_string()))?;
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user