Skip to content

Commit

Permalink
center: use clerk to handle auth progress
Browse files Browse the repository at this point in the history
  • Loading branch information
fuxiaohei committed Nov 2, 2023
1 parent 1087372 commit 07826c5
Show file tree
Hide file tree
Showing 10 changed files with 321 additions and 15 deletions.
37 changes: 37 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ land-dao = { path = "crates/dao", version = "0.1.8-beta.1" }
land-storage = { path = "crates/storage", version = "0.1.8-beta.1" }
anyhow = "1.0.75"
axum = { version = "0.6.20", features = ["headers", "ws"] }
axum-extra = { version = "0.8.0", features = ["cookie"] }
clap = { version = "4.4.6", features = ["derive", "env"] }
tokio = { version = "1.33.0", features = ["full"] }
tracing = "0.1.40"
Expand Down
4 changes: 4 additions & 0 deletions binary/center/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ authors = { workspace = true }
[dependencies]
anyhow = { workspace = true }
axum = { workspace = true }
axum-extra = { workspace = true }
axum-template = { version = "1.0.0", features = ["handlebars"] }
base64 = "0.21.5"
chrono = { workspace = true }
clap = { workspace = true }
envconfig = { workspace = true }
Expand All @@ -29,9 +31,11 @@ lettre = { version = "0.10.4", default-features = false, features = [
md-5 = { workspace = true }
mime_guess = "2.0.4"
once_cell = { workspace = true }
reqwest = { workspace = true }
rust-embed = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
time = { workspace = true }
tokio = { workspace = true }
tower-http = { version = "0.4.3", features = ["cors", "trace"] }
tracing = { workspace = true }
Expand Down
174 changes: 174 additions & 0 deletions binary/center/src/pages/auth.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
use axum::extract::Path;
use axum::response::{IntoResponse, Redirect};
use axum::{middleware::Next, response};
use axum_extra::extract::cookie::{Cookie, SameSite};
use axum_extra::extract::CookieJar;
use base64::{engine::general_purpose, Engine as _};
use hyper::{Request, StatusCode};
use land_dao::user::{create_by_oauth, find_by_oauth_id, OauthProvider};
use land_dao::user_token::{self, CreatedByCases};
use serde::{Deserialize, Serialize};
use tracing::{debug, error, info};

pub async fn session_auth_middleware<B>(
request: Request<B>,
next: Next<B>,
) -> Result<response::Response, StatusCode> {
let uri = request.uri().clone();
let path = uri.path();

// sign-in, sign-callback, sign-out should skip
// /static/
if path.starts_with("/sign-") || path.starts_with("/static/") {
return Ok(next.run(request).await);
}

let headers = request.headers();
let jar = CookieJar::from_headers(headers);
let session_id = jar
.get("__runtime_land_session")
.map(|c| c.value())
.unwrap_or_default();
if session_id.is_empty() {
return Ok(Redirect::to("/sign-in").into_response());
}
Ok(next.run(request).await)
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ClerkCallbackRequest {
session_id: String,
user_id: String,
user_image_url: String,
user_first_name: String,
user_full_name: String,
user_name: String,
user_email: String,
oauth_social: String,
redirect_to: String,
}

#[derive(Serialize, Deserialize)]
pub struct ClerkCallbackResponse {
pub ok: bool,
}

#[derive(Serialize, Deserialize)]
struct ClerkVerifySessionRequest {
token: String,
}

#[derive(Debug, Serialize, Deserialize)]
struct ClerkVerifySessionResponse {
id: String,
client_id: String,
user_id: String,
status: String,
last_active_at: u64,
expire_at: u64,
abandon_at: u64,
created_at: u64,
updated_at: u64,
}

async fn clerk_verify_session(req: &ClerkCallbackRequest, session: String) -> anyhow::Result<()> {
let verify_api = format!(
"https://api.clerk.dev/v1/sessions/{}/verify",
req.session_id,
);
let verify_data = ClerkVerifySessionRequest { token: session };
debug!("clerk-verify-session: {}", verify_api);
let resp = reqwest::Client::new()
.post(verify_api)
.header(
"Authorization",
"Bearer sk_test_mTylRXqX3ds2ZWPo2P3amunjDypN7B7Q6hxqjdEEbD",
)
.json(&verify_data)
.send()
.await?;
if !resp.status().is_success() {
return Err(anyhow::anyhow!(
"clerk-verify-session error: {}, {}",
resp.status(),
resp.text().await?
));
}
let resp: ClerkVerifySessionResponse = resp.json().await?;
debug!("clerk-verify-session-resp: {:?}", resp);
Ok(())
}

pub async fn clerk_callback(
jar: CookieJar,
Path(path): Path<String>,
) -> Result<(CookieJar, Redirect), StatusCode> {
// decode path string as ClerkCallbackRequest
info!("path: {}", path);
let data = match general_purpose::STANDARD.decode(path) {
Ok(v) => v,
Err(e) => {
error!("clerk-callback error: {}", e);
return Err(StatusCode::BAD_REQUEST);
}
};
let req = match serde_json::from_slice::<ClerkCallbackRequest>(&data) {
Ok(v) => v,
Err(e) => {
error!("clerk-callback error: {}", e);
return Err(StatusCode::BAD_REQUEST);
}
};
let redirect_to = req.redirect_to.clone();
let clerk_session = jar.get("__session").unwrap();
match clerk_verify_session(&req, clerk_session.value().to_string()).await {
Ok(_) => {}
Err(e) => {
error!("clerk-callback error: {}", e);
return Err(StatusCode::UNAUTHORIZED);
}
}
let session_id = match create_session_id(&req).await {
Ok(v) => v,
Err(e) => {
error!("clerk-callback error: {}", e);
return Err(StatusCode::INTERNAL_SERVER_ERROR);
}
};
let mut session_cookie = Cookie::new("__runtime_land_session", session_id);
session_cookie.set_max_age(Some(time::Duration::days(1)));
session_cookie.set_path("/");
session_cookie.set_same_site(Some(SameSite::Strict));
Ok((jar.add(session_cookie), Redirect::to(&redirect_to)))
}

async fn create_session_id(req: &ClerkCallbackRequest) -> anyhow::Result<String> {
let mut user = find_by_oauth_id(req.user_id.clone()).await?;
// first user login ,create this user
if user.is_none() {
debug!(
"create_session_id: create user by oauth, email:{}",
req.user_email
);
let user2 = create_by_oauth(
req.user_name.clone(),
req.user_full_name.clone(),
req.user_email.clone(),
req.user_image_url.clone(),
req.user_id.clone(),
OauthProvider::Clerk.to_string(),
req.oauth_social.clone(),
)
.await?;
user = Some(user2);
}
let user = user.unwrap();
let token = user_token::create(
user.id,
"dashboard-session".to_string(),
3600 * 23,
CreatedByCases::Session,
)
.await?;
Ok(token.value)
}
10 changes: 9 additions & 1 deletion binary/center/src/pages/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use anyhow::Result;
use axum::extract::Path;
use axum::middleware;
use axum::response::{IntoResponse, Response};
use axum::{body::Body, routing::get, Router};
use axum_extra::extract::cookie::CookieJar;
use axum_template::engine::Engine;
use axum_template::RenderHtml;
use handlebars::Handlebars;
use hyper::StatusCode;
use mime_guess::mime;
use tracing::debug;

mod auth;

// Type alias for our engine. For this example, we are using Handlebars
type AppEngine = Engine<Handlebars<'static>>;

Expand All @@ -18,8 +22,10 @@ pub fn router() -> Router {
.route("/projects", get(render_projects))
.route("/projects/:name", get(render_project_single))
.route("/sign-in", get(render_signin))
.route("/sign-callback/*path", get(auth::clerk_callback))
.route("/static/*path", get(render_static))
.with_state(Engine::from(hbs))
.route_layer(middleware::from_fn(auth::session_auth_middleware))
}

async fn render_static(Path(path): Path<String>) -> Response<Body> {
Expand All @@ -40,7 +46,9 @@ async fn render_static(Path(path): Path<String>) -> Response<Body> {
.unwrap()
}

async fn render_projects(engine: AppEngine) -> impl IntoResponse {
async fn render_projects(engine: AppEngine, jar: CookieJar) -> impl IntoResponse {
let clerk_session = jar.get("__session");
println!("clerk_session: {:?}", clerk_session);
RenderHtml("projects.hbs", engine, &())
}

Expand Down
13 changes: 12 additions & 1 deletion binary/center/templates/css/input.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
.alert-card {
@apply p-4 mb-4 text-sm text-red-800 rounded-lg bg-red-50;
}

#sign-in-alert {
@apply w-80 m-auto mb-6 text-2xl;
}

#top-nav {
@apply bg-white border-b border-gray-200 dark:bg-gray-900;
}
Expand Down Expand Up @@ -104,7 +112,10 @@
}

#sign-in-button {
@apply text-xl px-4 py-2 bg-primary rounded-lg text-white hover:bg-white hover:border hover:border-primary hover:text-primary;
@apply text-2xl px-4 py-2 bg-primary rounded-lg text-white hover:bg-white hover:border hover:border-primary hover:text-primary;
}
#sign-in-button:hover svg {
@apply text-primary;
}
#project-navbar {
@apply font-medium text-center text-gray-500 border-b border-gray-200;
Expand Down
6 changes: 1 addition & 5 deletions binary/center/templates/partials/meta.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,4 @@
<link rel="icon" type="image/png" href="/static/img/logo-v2.png" />
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.0.0/flowbite.min.css" rel="stylesheet" />
<link href="/static/css/main.css" rel="stylesheet" />
<title>Runtime.land</title>
<script async crossorigin="anonymous" data-clerk-publishable-key="pk_test_cGV0LW1vb3NlLTc1LmNsZXJrLmFjY291bnRzLmRldiQ"
onload="window.Clerk.load()"
src="https://pet-moose-75.clerk.accounts.dev/npm/@clerk/clerk-js@4/dist/clerk.browser.js" type="text/javascript">
</script>
<title>Runtime.land</title>
Loading

0 comments on commit 07826c5

Please sign in to comment.