feat: session token and dev ease

This commit is contained in:
Fu Zi Xiang 2023-10-10 12:30:57 +08:00
parent 4a407ada33
commit a293bd34ee
No known key found for this signature in database
11 changed files with 159 additions and 70 deletions

1
admin_frontend/dev.env Normal file
View File

@ -0,0 +1 @@
GOTRUE_URL=http://localhost:9998

View File

@ -0,0 +1,5 @@
# Development
- Run the dev docker compose in the previous directory: `docker compose --file docker-compose-dev.yml up -d`
- cp `dev.env` to `.env`
- run the frontend: `cargo watch -x run -w .`
- web will be served at `localhost:3000`

View File

@ -0,0 +1,32 @@
# Minimal nginx configuration for AppFlowy-Cloud
# Self Hosted AppFlowy Cloud user should alter this file to suit their needs
events {
worker_connections 1024;
}
http {
server {
listen 80;
server_name gotrue;
location / {
proxy_pass http://localhost:9998;
}
}
server {
listen 80;
# GoTrue
location ~ ^/(verify|authorize|callback|settings|user|token|admin) {
proxy_pass http://localhost:9998;
}
# Admin Frontend
location / {
proxy_pass http://localhost:3000;
}
}
}

View File

@ -1,26 +1,29 @@
use axum::{response::IntoResponse, Json};
use axum::{http::status, response::IntoResponse};
#[derive(serde::Serialize)]
// #[derive(serde::Serialize)]
pub struct WebApiError {
pub code: i16,
pub message: String,
pub status_code: status::StatusCode,
pub payload: String,
}
impl WebApiError {
pub fn new(code: i16, message: String) -> Self {
Self { code, message }
pub fn new(code: status::StatusCode, message: String) -> Self {
Self {
status_code: code,
payload: message,
}
}
}
impl IntoResponse for WebApiError {
fn into_response(self) -> axum::response::Response {
Json(self).into_response()
(self.status_code, self.payload).into_response()
}
}
impl From<gotrue_entity::GoTrueError> for WebApiError {
fn from(v: gotrue_entity::GoTrueError) -> Self {
WebApiError::new(500, v.to_string())
WebApiError::new(status::StatusCode::UNAUTHORIZED, v.to_string())
}
}

View File

@ -14,7 +14,7 @@ async fn main() {
let gotrue_client = gotrue::api::Client::new(
reqwest::Client::new(),
&std::env::var("GOTRUE_URL").unwrap_or("http://localhost:9999".to_string()),
&std::env::var("GOTRUE_URL").unwrap_or("http://gotrue:9999".to_string()),
);
let state = AppState { gotrue_client };

View File

@ -1,7 +1,12 @@
use serde::Deserialize;
use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct LoginRequest {
pub email: String,
pub password: String,
}
#[derive(Serialize)]
pub struct LoginResponse {
pub access_token: String,
}

View File

@ -10,6 +10,19 @@ where
pub data: T,
}
impl<T> WebApiResponse<T>
where
T: serde::Serialize,
{
pub fn new(data: T) -> Self {
Self {
code: 0,
message: "success".to_owned(),
data,
}
}
}
impl<T> IntoResponse for WebApiResponse<T>
where
T: serde::Serialize,

View File

@ -1,12 +1,10 @@
use std::borrow::Cow;
use axum::response::Result;
use axum::{extract::State, routing::post, Json, Router};
use axum_extra::extract::cookie::Cookie;
use axum_extra::extract::CookieJar;
use crate::error::WebApiError;
use crate::models::LoginResponse;
use crate::response::WebApiResponse;
use crate::{models::LoginRequest, AppState};
use axum::response::Result;
use axum::Json;
use axum::{extract::State, routing::post, Router};
pub fn router() -> Router<AppState> {
Router::new()
@ -17,10 +15,9 @@ pub fn router() -> Router<AppState> {
// TODO: Support OAuth2 login
// login and set the cookie
pub async fn login_handler(
cookie_jar: CookieJar,
State(state): State<AppState>,
Json(param): Json<LoginRequest>,
) -> Result<CookieJar, WebApiError> {
) -> Result<WebApiResponse<LoginResponse>, WebApiError> {
let token = state
.gotrue_client
.token(&gotrue::grant::Grant::Password(
@ -31,12 +28,7 @@ pub async fn login_handler(
))
.await?;
Ok(set_access_token_cookie(
cookie_jar,
token.access_token.into(),
))
}
fn set_access_token_cookie(jar: CookieJar, token: Cow<'static, str>) -> CookieJar {
jar.add(Cookie::new("access_token", token))
Ok(WebApiResponse::new(LoginResponse {
access_token: token.access_token,
}))
}

View File

@ -2,7 +2,7 @@ use crate::error::RenderError;
use askama::Template;
use axum::response::Result;
use axum::{response::Html, routing::get, Router};
use axum_extra::extract::cookie::CookieJar;
use axum_extra::extract::cookie::{Cookie, CookieJar};
use crate::templates;
@ -11,9 +11,14 @@ pub fn router() -> Router {
.route("/", get(home_handler))
.route("/home", get(home_handler))
.route("/login", get(login_handler))
// for testing and debugging
.route("/setcookie", get(set_cookie_handler))
.route("/resetcookie", get(reset_cookie_handler))
}
pub async fn home_handler(cookies: CookieJar) -> Result<Html<String>, RenderError> {
println!("cookies: {:?}", cookies);
let access_token = cookies.get("access_token");
match access_token {
Some(access_token) => Ok(Html(access_token.to_string())), // TODO: render home page
@ -25,3 +30,11 @@ pub async fn login_handler() -> Result<Html<String>, RenderError> {
let s = templates::Login {}.render()?;
Ok(Html(s))
}
pub async fn set_cookie_handler(jar: CookieJar) -> CookieJar {
jar.add(Cookie::new("access_token", "test"))
}
pub async fn reset_cookie_handler(jar: CookieJar) -> CookieJar {
jar.remove(Cookie::new("access_token", ""))
}

View File

@ -1,37 +1,62 @@
<html>
<script
src="https://unpkg.com/htmx.org@1.9.6"
integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"
crossorigin="anonymous"
></script>
<body>
<h1>Admin Login</h1>
<form>
<table>
<tr>
<td>
<label for="email">Email:</label>
</td>
<td>
<input type="text" id="email" name="email" required/>
</td>
<body>
<h1>Admin Login</h1>
<form id="loginForm">
<table>
<tr>
<td>
<label for="email">Email:</label>
</td>
<td>
<input type="text" id="email" name="email" required />
</td>
</tr>
<tr>
<td>
<label for="password">Password:</label>
</td>
<td>
<input type="password" id="password" name="password" required />
</td>
</tr>
</table>
<button type="button" id="submitBtn">Submit</button>
</form>
<div id="response"></div>
</tr>
<tr>
<td>
<label for="password">Password:</label>
</td>
<td>
<input type="password" id="password" name="password" required/>
</td>
<script>
document.getElementById('submitBtn').addEventListener('click', function () {
var data = {
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
fetch('/web-api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
})
.then(response => {
if (!response.ok) {
// If HTTP status code is not OK, throw an error with the status text
throw Error(response.statusText);
}
// Parse the JSON response
return response.json();
})
.then(data => {
// Set the token as a cookie
document.cookie = "access_token=" + data.data.access_token + "; path=/";
window.location.href = "/home";
})
.catch((error) => {
console.error('Error:', error);
document.getElementById('response').innerText = 'Login failed: ' + error.message;
});
});
</script>
</body>
</tr>
</table>
<button hx-post="/web-api/login" hx-target="#response">
Submit
</button>
</form>
<div id="response"></div>
</body>
</html>

View File

@ -8,7 +8,7 @@ services:
depends_on:
- appflowy_cloud
- gotrue
- admin_frontend
# - admin_frontend
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/ssl/certificate.crt:/etc/nginx/ssl/certificate.crt
@ -95,12 +95,12 @@ services:
ports:
- 8000:8000
admin_frontend:
restart: on-failure
build:
context: .
dockerfile: ./admin_frontend/Dockerfile
depends_on:
- gotrue
ports:
- 3000:3000
#admin_frontend:
# restart: on-failure
# build:
# context: .
# dockerfile: ./admin_frontend/Dockerfile
# depends_on:
# - gotrue
# ports:
# - 3000:3000