feat: added oauth login for admin (#119)
* doc: added deployment guide for appflowy cloud * feat: added oauth login for admin * feat: specify redirect_url * feat: implemented google oauth * fix: default value for redirect_to * fix: add check for location hash
This commit is contained in:
parent
88be0c2433
commit
d638c01763
47
README.md
47
README.md
|
|
@ -2,52 +2,9 @@
|
||||||
- Cloud Server for AppFlowy
|
- Cloud Server for AppFlowy
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
- See [deployment guide](./doc/deployment.md)
|
||||||
|
|
||||||
### Environmental Variables before starting
|
## Development
|
||||||
- you can set it explicitly(below) or in a `.env` file (use `dev.env`) as template
|
|
||||||
```bash
|
|
||||||
# authentication key, change this and keep the key safe and secret
|
|
||||||
GOTRUE_JWT_SECRET=secret_auth_pass
|
|
||||||
|
|
||||||
# enabled by default, if you dont want need email confirmation, set to false
|
|
||||||
GOTRUE_MAILER_AUTOCONFIRM=true
|
|
||||||
|
|
||||||
# if you enable mail confirmation, you need to set the SMTP configuration below
|
|
||||||
GOTRUE_SMTP_HOST=smtp.gmail.com
|
|
||||||
GOTRUE_SMTP_PORT=465
|
|
||||||
GOTRUE_SMTP_USER=email_sender@some_company.com
|
|
||||||
GOTRUE_SMTP_PASS=email_sender_password
|
|
||||||
GOTRUE_SMTP_ADMIN_EMAIL=comp_admin@@some_company.com
|
|
||||||
|
|
||||||
# Change 'localhost:9998' to the public host of machine that is running on.
|
|
||||||
# This is for email confirmation link
|
|
||||||
API_EXTERNAL_URL=http://localhost:9998
|
|
||||||
|
|
||||||
# Enable Google OAuth2, default: false, quick link for set up:
|
|
||||||
# https://console.cloud.google.com/apis/credentials
|
|
||||||
# https://console.cloud.google.com/apis/credentials/consent
|
|
||||||
GOTRUE_EXTERNAL_GOOGLE_ENABLED=false
|
|
||||||
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=some_id
|
|
||||||
GOTRUE_EXTERNAL_GOOGLE_SECRET=some_secret
|
|
||||||
# Change 'localhost:9998' to the public host of machine that is running on.
|
|
||||||
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=http://localhost:9998/callback
|
|
||||||
```
|
|
||||||
- additional settings can be modified in `docker-compose.yml`
|
|
||||||
## SSL Certificate
|
|
||||||
- To use your own SSL, replace `certificate.crt` and `private_key.key`
|
|
||||||
with your own in `nginx/ssl/` directory
|
|
||||||
|
|
||||||
### Start Cloud Server
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
### Ports
|
|
||||||
Host Server is required to expose the following Ports:
|
|
||||||
- `443` (https)
|
|
||||||
- `80` (http)
|
|
||||||
|
|
||||||
## Local Development
|
|
||||||
|
|
||||||
### Pre-requisites
|
### Pre-requisites
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
# 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -4,7 +4,7 @@ use crate::response::WebApiResponse;
|
||||||
use crate::session::{self, UserSession};
|
use crate::session::{self, UserSession};
|
||||||
use crate::{models::LoginRequest, AppState};
|
use crate::{models::LoginRequest, AppState};
|
||||||
use axum::extract::Path;
|
use axum::extract::Path;
|
||||||
use axum::http::status;
|
use axum::http::{status, HeaderMap, HeaderValue};
|
||||||
use axum::response::Result;
|
use axum::response::Result;
|
||||||
use axum::Json;
|
use axum::Json;
|
||||||
use axum::{extract::State, routing::post, Router};
|
use axum::{extract::State, routing::post, Router};
|
||||||
|
|
@ -19,9 +19,38 @@ pub fn router() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
// TODO
|
// TODO
|
||||||
.route("/login", post(login_handler))
|
.route("/login", post(login_handler))
|
||||||
|
.route("/login_refresh/:refresh_token", post(login_refresh_handler))
|
||||||
.route("/logout", post(logout_handler))
|
.route("/logout", post(logout_handler))
|
||||||
.route("/user/:param", post(post_user_handler).delete(delete_user_handler).put(put_user_handler))
|
.route("/user/:param", post(post_user_handler).delete(delete_user_handler).put(put_user_handler))
|
||||||
.route("/user/:email/generate-link", post(post_user_generate_link_handler))
|
.route("/user/:email/generate-link", post(post_user_generate_link_handler))
|
||||||
|
.route("/oauth_login/:provider", post(post_oauth_login_handler))
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_HOST: HeaderValue = HeaderValue::from_static("localhost");
|
||||||
|
static DEFAULT_SCHEME: HeaderValue = HeaderValue::from_static("http");
|
||||||
|
pub async fn post_oauth_login_handler(
|
||||||
|
header_map: HeaderMap,
|
||||||
|
Path(provider): Path<String>,
|
||||||
|
) -> Result<WebApiResponse<String>, WebApiError<'static>> {
|
||||||
|
let host = header_map
|
||||||
|
.get("host")
|
||||||
|
.unwrap_or(&DEFAULT_HOST)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
let scheme = header_map
|
||||||
|
.get("x-scheme")
|
||||||
|
.unwrap_or(&DEFAULT_SCHEME)
|
||||||
|
.to_str()
|
||||||
|
.unwrap();
|
||||||
|
let base_url = format!("{}://{}", scheme, host);
|
||||||
|
|
||||||
|
let oauth_url = format!(
|
||||||
|
"{}/authorize?provider={}&redirect_uri={}",
|
||||||
|
base_url,
|
||||||
|
&provider,
|
||||||
|
format!("{}/web/oauth_login_redirect", base_url)
|
||||||
|
);
|
||||||
|
Ok(oauth_url.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn put_user_handler(
|
pub async fn put_user_handler(
|
||||||
|
|
@ -98,6 +127,32 @@ pub async fn post_user_handler(
|
||||||
Ok(user.into())
|
Ok(user.into())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn login_refresh_handler(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
jar: CookieJar,
|
||||||
|
Path(refresh_token): Path<String>,
|
||||||
|
) -> Result<CookieJar, WebApiError<'static>> {
|
||||||
|
let token = state
|
||||||
|
.gotrue_client
|
||||||
|
.token(&gotrue::grant::Grant::RefreshToken(
|
||||||
|
gotrue::grant::RefreshTokenGrant { refresh_token },
|
||||||
|
))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let new_session_id = uuid::Uuid::new_v4();
|
||||||
|
let new_session = session::UserSession::new(
|
||||||
|
new_session_id.to_string(),
|
||||||
|
token.access_token.to_string(),
|
||||||
|
token.refresh_token.to_owned(),
|
||||||
|
);
|
||||||
|
state.session_store.put_user_session(&new_session).await?;
|
||||||
|
|
||||||
|
let mut cookie = Cookie::new("session_id", new_session_id.to_string());
|
||||||
|
cookie.set_path("/");
|
||||||
|
|
||||||
|
Ok(jar.add(cookie))
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: Support OAuth2 login
|
// TODO: Support OAuth2 login
|
||||||
// login and set the cookie
|
// login and set the cookie
|
||||||
pub async fn login_handler(
|
pub async fn login_handler(
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@
|
||||||
</form>
|
</form>
|
||||||
<div id="response"></div>
|
<div id="response"></div>
|
||||||
|
|
||||||
|
<div id="oauth2Login">
|
||||||
|
<p>Or login with:</p>
|
||||||
|
<a href="/authorize?provider=google&redirect_to=/web/login">Google</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document
|
document
|
||||||
.getElementById("submitBtn")
|
.getElementById("submitBtn")
|
||||||
|
|
@ -55,6 +60,31 @@
|
||||||
).innerText = `Login failed: ${error.message}`;
|
).innerText = `Login failed: ${error.message}`;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (window.location.hash) {
|
||||||
|
// OAuth
|
||||||
|
// Extract data from the URL fragment
|
||||||
|
const fragmentData = window.location.hash.substring(1); // Remove the leading #
|
||||||
|
const fragmentParams = new URLSearchParams(fragmentData); // Parse the fragment data as a URLSearchParams object
|
||||||
|
const refreshToken = fragmentParams.get("refresh_token"); // Extract the refresh_token
|
||||||
|
fetch(`/web-api/login_refresh/${refreshToken}`, {
|
||||||
|
// Login in via refresh_token
|
||||||
|
method: "POST",
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (!response.ok) {
|
||||||
|
// If HTTP status code is not OK, throw an error with the status text
|
||||||
|
throw Error(response.statusText);
|
||||||
|
}
|
||||||
|
window.location.href = "/web/home";
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error(`Error:, ${error}`);
|
||||||
|
document.getElementById(
|
||||||
|
"response",
|
||||||
|
).innerText = `OAuth Login failed: ${error.message}`;
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# Docs
|
||||||
|
- Directory to contain information about usage and development.
|
||||||
|
- [Appflowy Cloud Deployment](./deployment.md)
|
||||||
|
- [Appflowy with Cloud](./integration.md)
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
# Deployment
|
||||||
|
- AppFlowy-Cloud is designed to be easily self deployed for self managed cloud storage
|
||||||
|
- The following document will walk you through the steps to deploy your own AppFlowy-Cloud
|
||||||
|
|
||||||
|
## Hardware Requirements
|
||||||
|
- Because AppFlowy-Cloud will have to be running persistently (or at least when one of the user is using),
|
||||||
|
we recommend using cloud compute services (as your host server) such as
|
||||||
|
- [Amazon EC2](https://aws.amazon.com/ec2/) or
|
||||||
|
- [Azure Virtual Machines](https://azure.microsoft.com/en-gb/products/virtual-machines/)
|
||||||
|
- Minimum 2GB Ram (4GB Recommended)
|
||||||
|
- Ports 80/443 available
|
||||||
|
|
||||||
|
## Software Requirements
|
||||||
|
- [docker compose](https://docs.docker.com/compose)
|
||||||
|
This is needed be installed in your host server
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
### 1. Getting source files
|
||||||
|
- Clone this repository into your host server and `cd` into it
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/AppFlowy-IO/AppFlowy-Cloud`
|
||||||
|
cd AppFlowy-Cloud`
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Preparing the configuration
|
||||||
|
- This is perhaps the most important part of the deployment process, please read carefully.
|
||||||
|
- It is required that that is a `.env` file in the root directory of the repository.
|
||||||
|
- To get started, copy the template `dev.env` as `.env` using the following shell commands:
|
||||||
|
```bash
|
||||||
|
cp dev.env .env
|
||||||
|
```
|
||||||
|
- There will be values in the `.env` that needs to be change according to your needs
|
||||||
|
- Kindly read the following comments for each set of settings
|
||||||
|
```bash
|
||||||
|
# This is the secret key for authentication, please change this and keep the key safe
|
||||||
|
GOTRUE_JWT_SECRET=hello456
|
||||||
|
|
||||||
|
# This determine if the user will be user automatically be confirmed when they sign up
|
||||||
|
# If this is enabled, it requires a clicking a confirmation link in the email which user
|
||||||
|
# use for sign up.
|
||||||
|
# Pre-requisite if you enable: you need to have your SMTP Service set up,
|
||||||
|
# which you can then fill in the details below
|
||||||
|
GOTRUE_MAILER_AUTOCONFIRM=true
|
||||||
|
|
||||||
|
# if you enable mail confirmation, you need to set the SMTP configuration below
|
||||||
|
GOTRUE_SMTP_HOST=smtp.gmail.com
|
||||||
|
GOTRUE_SMTP_PORT=465
|
||||||
|
GOTRUE_SMTP_USER=user1@example.com
|
||||||
|
# this is typically an app password that you would need to generate: https://myaccount.google.com/apppasswords
|
||||||
|
GOTRUE_SMTP_PASS=somesecretkey
|
||||||
|
# You can leave this field same as GOTRUE_SMTP_USER
|
||||||
|
GOTRUE_SMTP_ADMIN_EMAIL=user1@example.com
|
||||||
|
|
||||||
|
# This is the email account that is the admin account
|
||||||
|
# which has the highest privilege level, typically use to
|
||||||
|
# manage other users, such as user creation, deletion, password change, etc
|
||||||
|
GOTRUE_ADMIN_EMAIL=admin@example.com
|
||||||
|
GOTRUE_ADMIN_PASSWORD=password
|
||||||
|
|
||||||
|
# This is the address of the authentication server
|
||||||
|
# which is the same as the public IP/hostname of your host server
|
||||||
|
# when an email confirmation link is click, this is the host that user's devices
|
||||||
|
# will try to connect to
|
||||||
|
API_EXTERNAL_URL=http://localhost:9998
|
||||||
|
|
||||||
|
# 2 fields below are only relevant for development, can ignore
|
||||||
|
DATABASE_URL=postgres://postgres:password@localhost:5433/postgres
|
||||||
|
SQLX_OFFLINE=false
|
||||||
|
|
||||||
|
# Google OAuth2
|
||||||
|
# This enables login using user's google account
|
||||||
|
# To set up, you need to go the following sites:
|
||||||
|
# https://console.cloud.google.com/apis/credentials/consent
|
||||||
|
# https://console.cloud.google.com/apis/credentials -> create credentials -> create oauth client ID
|
||||||
|
# in the field `Authorised redirect URIs`, you should put `<your host server public ip/hostname>/callback`
|
||||||
|
GOTRUE_EXTERNAL_GOOGLE_ENABLED=false
|
||||||
|
GOTRUE_EXTERNAL_GOOGLE_CLIENT_ID=
|
||||||
|
GOTRUE_EXTERNAL_GOOGLE_SECRET=
|
||||||
|
GOTRUE_EXTERNAL_GOOGLE_REDIRECT_URI=http://localhost:9998/callback
|
||||||
|
|
||||||
|
# File Storage
|
||||||
|
# This affects where the files will be uploaded.
|
||||||
|
# By default, Minio will be deployed as file storage server # and it will use the host server's disk storage.
|
||||||
|
# You can also AWS S3 by setting USE_MINIO as false
|
||||||
|
USE_MINIO=true # determine if minio-server is used
|
||||||
|
# MINIO_URL=http://localhost:9000 # change this to use minio from a different host (e.g. maybe you self host Minio somewhere)
|
||||||
|
AWS_ACCESS_KEY_ID=minioadmin
|
||||||
|
AWS_SECRET_ACCESS_KEY=minioadmin
|
||||||
|
AWS_S3_BUCKET=appflowy
|
||||||
|
AWS_REGION=us-east-1 # This option only applicable for AWS S3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Running the services
|
||||||
|
|
||||||
|
### Start and run AppFlowy-Cloud
|
||||||
|
- The following command will build and start the AppFlowy-Cloud
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
- Please check that all the services are running
|
||||||
|
```bash
|
||||||
|
docker ps -a
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Reconfiguring and redeployment
|
||||||
|
- It is very common to reconfigure and restart. To do so, simply edit the `.env` and do `docker compose up -d` again
|
||||||
|
|
||||||
|
## Ports
|
||||||
|
- After Deployment, you should see that AppFlowy-Cloud is serving 2 ports
|
||||||
|
- `443` (https)
|
||||||
|
- `80` (http)
|
||||||
|
- Your host server need to expose either of the port
|
||||||
|
|
||||||
|
## SSL Certificate
|
||||||
|
- To use your own SSL certications for https, replace `certificate.crt` and `private_key.key`
|
||||||
|
with your own in `nginx/ssl/` directory
|
||||||
|
|
||||||
|
## Usage of AppFlowy Application with AppFlowy Cloud
|
||||||
|
- [AppFlowy with AppFlowyCloud](./integration.md)
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
# Using AppFlowy with AppFlowy Cloud
|
||||||
|
|
@ -46,7 +46,8 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
environment:
|
environment:
|
||||||
- GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application
|
- GOTRUE_SITE_URL= # redirected to AppFlowy application
|
||||||
|
- URI_ALLOW_LIST=* # adjust restrict if necessary
|
||||||
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
|
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
|
||||||
- GOTRUE_DB_DRIVER=postgres
|
- GOTRUE_DB_DRIVER=postgres
|
||||||
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
|
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
|
||||||
|
|
|
||||||
|
|
@ -60,7 +60,8 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
environment:
|
environment:
|
||||||
- GOTRUE_SITE_URL=appflowy-flutter:// # redirected to AppFlowy application
|
- GOTRUE_SITE_URL= # redirected to AppFlowy application
|
||||||
|
- URI_ALLOW_LIST=* # adjust restrict if necessary
|
||||||
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
|
- GOTRUE_JWT_SECRET=${GOTRUE_JWT_SECRET} # authentication secret
|
||||||
- GOTRUE_DB_DRIVER=postgres
|
- GOTRUE_DB_DRIVER=postgres
|
||||||
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
|
- API_EXTERNAL_URL=${API_EXTERNAL_URL}
|
||||||
|
|
|
||||||
|
|
@ -2,5 +2,5 @@ FROM golang
|
||||||
WORKDIR /go/src/supabase
|
WORKDIR /go/src/supabase
|
||||||
RUN git clone https://github.com/supabase/gotrue.git
|
RUN git clone https://github.com/supabase/gotrue.git
|
||||||
WORKDIR /go/src/supabase/gotrue
|
WORKDIR /go/src/supabase/gotrue
|
||||||
RUN git checkout v2.95.2 && go install
|
RUN git checkout v2.99.0 && go install
|
||||||
CMD ["gotrue"]
|
CMD ["gotrue"]
|
||||||
|
|
|
||||||
|
|
@ -124,28 +124,14 @@ impl Client {
|
||||||
.try_for_each(|f| -> Result<(), AppError> {
|
.try_for_each(|f| -> Result<(), AppError> {
|
||||||
let (k, v) = f.split_once('=').ok_or(url_missing_param("key=value"))?;
|
let (k, v) = f.split_once('=').ok_or(url_missing_param("key=value"))?;
|
||||||
match k {
|
match k {
|
||||||
"access_token" => {
|
"access_token" => access_token = Some(v.to_string()),
|
||||||
access_token = Some(v.to_string());
|
"token_type" => token_type = Some(v.to_string()),
|
||||||
},
|
"expires_in" => expires_in = Some(v.parse::<i64>().context("parser expires_in failed")?),
|
||||||
"token_type" => {
|
"expires_at" => expires_at = Some(v.parse::<i64>().context("parser expires_at failed")?),
|
||||||
token_type = Some(v.to_string());
|
"refresh_token" => refresh_token = Some(v.to_string()),
|
||||||
},
|
"provider_access_token" => provider_access_token = Some(v.to_string()),
|
||||||
"expires_in" => {
|
"provider_refresh_token" => provider_refresh_token = Some(v.to_string()),
|
||||||
expires_in = Some(v.parse::<i64>().context("parser expires_in failed")?);
|
x => tracing::warn!("unhandled param in url: {}", x),
|
||||||
},
|
|
||||||
"expires_at" => {
|
|
||||||
expires_at = Some(v.parse::<i64>().context("parser expires_at failed")?);
|
|
||||||
},
|
|
||||||
"refresh_token" => {
|
|
||||||
refresh_token = Some(v.to_string());
|
|
||||||
},
|
|
||||||
"provider_access_token" => {
|
|
||||||
provider_access_token = Some(v.to_string());
|
|
||||||
},
|
|
||||||
"provider_refresh_token" => {
|
|
||||||
provider_refresh_token = Some(v.to_string());
|
|
||||||
},
|
|
||||||
_ => {},
|
|
||||||
};
|
};
|
||||||
Ok(())
|
Ok(())
|
||||||
})?;
|
})?;
|
||||||
|
|
@ -196,7 +182,7 @@ impl Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(format!(
|
Ok(format!(
|
||||||
"{}/authorize?provider={}",
|
"{}/authorize?provider={}&redirect_to=appflowy-flutter://",
|
||||||
self.gotrue_client.base_url,
|
self.gotrue_client.base_url,
|
||||||
provider.as_str(),
|
provider.as_str(),
|
||||||
))
|
))
|
||||||
|
|
@ -760,10 +746,10 @@ pub fn extract_sign_in_url(html_str: &str) -> Result<String, anyhow::Error> {
|
||||||
let url = fragment
|
let url = fragment
|
||||||
.select(&selector)
|
.select(&selector)
|
||||||
.next()
|
.next()
|
||||||
.ok_or(anyhow!("no a tag found"))?
|
.ok_or(anyhow!("no a tag found in html: {}", html_str))?
|
||||||
.value()
|
.value()
|
||||||
.attr("href")
|
.attr("href")
|
||||||
.ok_or(anyhow!("no href found"))?
|
.ok_or(anyhow!("no href found in html: {}", html_str))?
|
||||||
.to_string();
|
.to_string();
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ pub struct AdminUserParams {
|
||||||
pub ban_duration: String,
|
pub ban_duration: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
#[derive(Deserialize, Serialize)]
|
||||||
pub struct GenerateLinkParams {
|
pub struct GenerateLinkParams {
|
||||||
#[serde(rename = "type")]
|
#[serde(rename = "type")]
|
||||||
pub type_: GenerateLinkType,
|
pub type_: GenerateLinkType,
|
||||||
|
|
@ -34,12 +34,23 @@ pub struct GenerateLinkParams {
|
||||||
pub redirect_to: String,
|
pub redirect_to: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Deserialize, Serialize)]
|
impl Default for GenerateLinkParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
GenerateLinkParams {
|
||||||
|
type_: GenerateLinkType::MagicLink,
|
||||||
|
email: String::default(),
|
||||||
|
new_email: String::default(),
|
||||||
|
password: String::default(),
|
||||||
|
data: BTreeMap::new(),
|
||||||
|
redirect_to: "appflowy-flutter://".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize)]
|
||||||
#[serde(rename_all = "lowercase")]
|
#[serde(rename_all = "lowercase")]
|
||||||
pub enum GenerateLinkType {
|
pub enum GenerateLinkType {
|
||||||
#[default]
|
|
||||||
MagicLink,
|
MagicLink,
|
||||||
|
|
||||||
Recovery,
|
Recovery,
|
||||||
Invite,
|
Invite,
|
||||||
Signup,
|
Signup,
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,9 @@ http {
|
||||||
|
|
||||||
# Admin Frontend
|
# Admin Frontend
|
||||||
location / {
|
location / {
|
||||||
|
proxy_set_header X-Scheme $scheme;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
|
||||||
proxy_pass http://admin_frontend:3000;
|
proxy_pass http://admin_frontend:3000;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue