feat: session token and dev ease
This commit is contained in:
parent
4a407ada33
commit
a293bd34ee
|
|
@ -0,0 +1 @@
|
|||
GOTRUE_URL=http://localhost:9998
|
||||
|
|
@ -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`
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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", ""))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue