3 个不稳定版本
0.2.0 | 2024年6月25日 |
---|---|
0.1.1 | 2024年3月21日 |
0.1.0 | 2024年3月20日 |
#1381 in 网页编程
105KB
1K SLoC
Loco OAuth2
Loco OAuth2 是一个用于 Loco API 的简单 OAuth2 初始化器。它设计成一个小巧且易于使用的库,用于在您的应用程序中实现 OAuth2。
文档
官方的 RFC 6749
OAuth2 文档可以在这里找到。
Shuttle 教程可以在这里找到。
OAuth2 是什么?
OAuth2 是一种协议,允许用户授予第三方网站或应用程序访问其受保护资源的权限,而无需透露其长期凭证或身份。为此,用户需要通过第三方网站进行身份验证并授权客户端应用程序访问。
授权类型
OAuth2 中有多种授权类型。目前 Loco OAuth2 支持的授权类型有 Authorization Code Grant
。客户端凭证授权、隐式授权等将在未来的版本中提供。
目录
安装
Cargo
cargo add loco-oauth2
或 Cargo.toml
[dependencies]
loco-oauth2 = "0.2.0"
术语表
OAuth2ClientGrantEnum |
OAuth2ClientGrantEnum 是不同 OAuth2 授权类型的枚举,OAuth2 客户端将属于 OAuth2ClientGrantEnum 之一。 |
OAuth2ClientStore |
用于管理一个或多个 OAuth2 客户端的抽象实现。 |
authorization_code::客户端 |
使用授权代码授权的客户端。 |
配置(授权代码授权)
生成一个私有的 cookie 密钥
secret_key 用于加密私有 cookie jar。它必须超过 64 字节。如果没有提供,它将被自动生成。以下是如何生成私有 cookie 密钥的示例。
# Cargo.toml
rand = "0.9.0-alpha.1"
axum-extra = { version = "0.9.3", features = ["cookie-private"]}
// src/main.rs
use axum_extra::extract::cookie::Key;
use rand::{Rng, thread_rng};
fn main() {
// Generate a cryptographically random key of 64 bytes
let mut rng = thread_rng();
let mut random_key = [0u8; 64];
rng.fill(&mut random_key);
match Key::try_from(&random_key[..]) {
Ok(key) => {
println!("Random key: {:?}", key.master());
}
Err(e) => {
println!("Error: {:?}", e);
}
}
}
OAuth2 配置
OAuth2 配置在 config/*.yaml
文件中完成。使用 oauth2
部分配置 OAuth2 客户端。
本示例使用Google Cloud作为OAuth2提供者。您需要一个Google Cloud项目,并使用OAuth客户端ID选项创建用于client_id
和client_secret
的OAuth2凭证。redirect_url
是提供者的服务器回调端点,应在创建OAuth2客户端ID时设置在Authorised redirect URIs
部分。
# config/*.yaml
# Initializers Configuration
initializers:
oauth2:
secret_key: {{get_env(name="OAUTH_PRIVATE_KEY", default="144, 76, 183, 1, 15, 184, 233, 174, 214, 251, 190, 186, 122, 61, 74, 84, 225, 110, 189, 115, 10, 251, 133, 128, 52, 46, 15, 66, 85, 1, 245, 73, 27, 113, 189, 15, 209, 205, 61, 100, 73, 31, 18, 58, 235, 105, 141, 36, 70, 92, 231, 151, 27, 32, 243, 117, 30, 244, 110, 89, 233, 196, 137, 130")}} # Optional, key for Private Cookie Jar, must be more than 64 bytes
authorization_code: # Authorization code grant type
- client_identifier: google # Identifier for the OAuth2 provider. Replace 'google' with your provider's name if different, must be unique within the oauth2 config.
client_credentials:
client_id: {{get_env(name="OAUTH_CLIENT_ID", default="oauth_client_id")}} # Replace with your OAuth2 client ID.
client_secret: {{get_env(name="OAUTH_CLIENT_SECRET", default="oauth_client_secret")}} # Replace with your OAuth2 client secret.
url_config:
auth_url: {{get_env(name="AUTH_URL", default="https://127.0.0.1/o/oauth2/auth")}} # authorization endpoint from the provider
token_url: {{get_env(name="TOKEN_URL", default="https://www.googleapis.com/oauth2/v3/token")}} # token endpoint from the provider for exchanging the authorization code for an access token
redirect_url: {{get_env(name="REDIRECT_URL", default="https://127.0.0.1:3000/api/oauth2/google/callback/cookie")}} # server callback endpoint for the provider, for default jwt route use 'default="https://127.0.0.1:3000/api/oauth2/google/callback/cookie"'
profile_url: {{get_env(name="PROFILE_URL", default="https://openidconnect.googleapis.com/v1/userinfo")}} # user profile endpoint from the provider for getting user data
scopes:
- {{get_env(name="SCOPES_1", default="https://www.googleapis.com/auth/userinfo.email")}} # Scopes for requesting access to user data
- {{get_env(name="SCOPES_2", default="https://www.googleapis.com/auth/userinfo.profile")}} # Scopes for requesting access to user data
cookie_config:
protected_url: {{get_env(name="PROTECTED_URL", default="https://127.0.0.1:3000/api/oauth2/protected")}} # Optional for jwt - For redirecting to protect url in cookie to prevent XSS attack
timeout_seconds: 600 # Optional, default 600 seconds
初始化
我们将使用Loco框架的初始化功能来初始化OAuth2客户端。
首先,我们需要创建一个用于存储csrf token的会话存储。我们将使用AxumSessionStore
来完成此任务。我们将为AxumSessionStore
创建一个新的初始化器结构体并实现Initializer
特质。
# Cargo.toml
# axum sessions
axum_session = { version = "0.14.0" }
// src/initializers/axum_session.rs
use async_trait::async_trait;
use axum::Router as AxumRouter;
use loco_rs::prelude::*;
pub struct AxumSessionInitializer;
#[async_trait]
impl Initializer for AxumSessionInitializer {
fn name(&self) -> String {
"axum-session".to_string()
}
async fn after_routes(&self, router: AxumRouter, _ctx: &AppContext) -> Result<AxumRouter> {
// Create the session store configuration
let session_config =
axum_session::SessionConfig::default().with_table_name("sessions_table");
// Create the session store
let session_store =
axum_session::SessionStore::<axum_session::SessionNullPool>::new(None, session_config)
.await
.unwrap();
// Add the session store to the AxumRouter as an extension
let router = router.layer(axum_session::SessionLayer::new(session_store));
Ok(router)
}
}
我们将为OAuth2ClientStore
创建一个新的初始化器结构体并实现Initializer
特质。在after_routes
函数中,我们将从config
获取oauth2
设置并创建OAuth2ClientStore
,并将其作为扩展添加到AxumRouter
。
// src/initializers/oauth2.rs
use axum::{async_trait, Extension, Router as AxumRouter};
use loco_oauth2::{config::Config, OAuth2ClientStore};
use loco_rs::prelude::*;
pub struct OAuth2StoreInitializer;
#[async_trait]
impl Initializer for OAuth2StoreInitializer {
fn name(&self) -> String {
"oauth2-store".to_string()
}
async fn after_routes(&self, router: AxumRouter, ctx: &AppContext) -> Result<AxumRouter> {
// Get all the settings from the config
let settings = ctx.config.initializers.clone().ok_or_else(|| {
Error::Message("Initializers config not configured for OAuth2".to_string())
})?;
// Get the oauth2 config in json format
let oauth2_config_value = settings
.get("oauth2")
.ok_or(Error::Message(
"Oauth2 config not found in Initializer configuration".to_string(),
))?
.clone();
// Convert the oauth2 config json to OAuth2Config
let oauth2_config: Config = oauth2_config_value.try_into().map_err(|e| {
tracing::error!(error = ?e, "could not convert oauth2 config from yaml");
Error::Message("could not convert oauth2 config from yaml".to_string())
})?;
// Create the OAuth2ClientStore
let oauth2_store = OAuth2ClientStore::new(oauth2_config).map_err(|e| {
tracing::error!(error = ?e, "could not create oauth2 store from config");
Error::Message("could not create oauth2 store from config".to_string())
})?;
// Add the OAuth2ClientStore to the AxumRouter as an extension
Ok(router.layer(Extension(oauth2_store)))
}
}
不要忘记将初始化器添加到App
结构体。
// src/app.rs
pub struct App;
#[async_trait]
impl Hooks for App {
async fn initializers(_ctx: &AppContext) -> Result<Vec<Box<dyn Initializer>>> {
Ok(vec![
Box::new(initializers::axum_session::AxumSessionInitializer),
Box::new(initializers::oauth2::OAuth2StoreInitializer),
])
}
}
迁移
安装
需要在迁移文件夹中安装loco-oauth2
库。
# Within migration folder
cargo add loco-oauth2
迁移脚本
需要迁移以创建用于OAuth2ClientStore
的o_auth2_sessions
表。
o_auth2_sessions
表用于将用户连接到OAuth2会话,并用于存储会话数据。
您可以从loco_oauth2
导入迁移脚本到您的迁移文件夹,而不是手动创建表。
// migration/src/lib.rs
use loco_oauth2::migration;
pub use sea_orm_migration::prelude::*;
pub struct Migrator;
#[async_trait::async_trait]
impl MigratorTrait for Migrator {
fn migrations() -> Vec<Box<dyn MigrationTrait>> {
vec![
Box::new(m20220101_000001_users::Migration),
Box::new(m20231103_114510_notes::Migration),
// Register OAuth2 sessions migration here
Box::new(migration::m20240101_000000_oauth2_sessions::Migration),
]
}
}
以下是o_auth2_sessions
表的结构。
CREATE TABLE o_auth2_sessions
(
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
id SERIAL PRIMARY KEY,
session_id VARCHAR NOT NULL,
expires_at TIMESTAMP NOT NULL,
user_id INTEGER NOT NULL
CONSTRAINT "fk-sessions-users"
REFERENCES users
ON UPDATE CASCADE
ON DELETE CASCADE
);
使用以下命令运行迁移。
cargo loco db migrate
然后使用以下命令生成所有模型。
cargo loco db entities
模型
从OAuth2提供者返回的用户详细信息形状取决于您的设置范围。以下是Google OAuth2提供者的用户模型示例。您可以根据所需内容更改字段。有关作用域的更多信息,请参阅Google作用域
// src/models/user.rs
/// `OAuth2UserProfile` user profile information via scopes
/// https://developers.google.com/identity/openid-connect/openid-connect#obtainuserinfo
#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct OAuth2UserProfile {
// https://www.googleapis.com/auth/userinfo.email See your primary Google Account email address
pub email: String,
// https://www.googleapis.com/auth/userinfo.profile See your personal info, including any personal info you've made publicly available
pub name: String,
// sub field is unique
pub sub: String,
pub email_verified: bool,
pub given_name: Option<String>, // Some accounts don't have this field
pub family_name: Option<String>, // Some accounts don't have this field
pub picture: Option<String>, // Some accounts don't have this field
pub locale: Option<String>, // Some accounts don't have this field
}
接下来,我们需要为users::Model
模型和o_auth2_sessions::Model
实现2个特质。
OAuth2UserTrait
示例
// src/models/users.rs
use loco_oauth2::models::users::OAuth2UserTrait;
use loco_rs::{auth::jwt, hash, prelude::*};
use super::o_auth2_sessions;
use async_trait::async_trait;
use chrono::offset::Local;
#[async_trait]
impl OAuth2UserTrait<OAuth2UserProfile> for Model {
/// Asynchronously finds user by OAuth2 session id.
/// # Arguments
/// * `db` - Database connection
/// * `cookie` - OAuth2 session id
///
/// # Returns
/// * `Self` - The `OAuth2UserTrait` struct
///
/// # Errors
/// * `ModelError` - When could not find the user in the DB
async fn find_by_oauth2_session_id(
db: &DatabaseConnection,
session_id: &str,
) -> ModelResult<Self> {
// find the session by the session id
let session = o_auth2_sessions::Entity::find()
.filter(super::_entities::o_auth2_sessions::Column::SessionId.eq(session_id))
.one(db)
.await?
.ok_or_else(|| ModelError::EntityNotFound)?;
// if the session is found, find the user by the user id
let user = users::Entity::find()
.filter(users::Column::Id.eq(session.user_id))
.one(db)
.await?;
user.ok_or_else(|| ModelError::EntityNotFound)
}
/// Asynchronously upsert user with OAuth data and saves it to the
/// database.
/// # Arguments
/// * `db` - Database connection
/// * `profile` - OAuth profile
///
/// # Returns
/// * `Self` - The `OAuth2UserTrait` struct
///
/// # Errors
///
/// When could not save the user into the DB
async fn upsert_with_oauth(
db: &DatabaseConnection,
profile: &OAuth2UserProfile,
) -> ModelResult<Self> {
let txn = db.begin().await?;
let user = match users::Entity::find()
.filter(users::Column::Email.eq(&profile.email))
.one(&txn)
.await?
{
None => {
// We use the sub field as the user fake password since sub is unique
let password_hash =
hash::hash_password(&profile.sub).map_err(|e| ModelError::Any(e.into()))?;
// Create the user into the database
users::ActiveModel {
email: ActiveValue::set(profile.email.to_string()),
name: ActiveValue::set(profile.name.to_string()),
email_verified_at: ActiveValue::set(Some(Local::now().naive_local())),
password: ActiveValue::set(password_hash),
..Default::default()
}
.insert(&txn)
.await
.map_err(|e| {
tracing::error!("Error while trying to create user: {e}");
ModelError::Any(e.into())
})?
}
// Do nothing if user exists
Some(user) => user,
};
txn.commit().await?;
Ok(user)
}
/// Generates a JWT
/// # Arguments
/// * `secret` - JWT secret
/// * `expiration` - JWT expiration time
///
/// # Returns
/// * `String` - JWT token
///
/// # Errors
/// * `ModelError` - When could not generate the JWT
fn generate_jwt(&self, secret: &str, expiration: &u64) -> ModelResult<String> {
self.generate_jwt(secret, expiration)
}
}
OAuth2SessionsTrait
示例
// src/models/o_auth2_sessions.rs
pub use super::_entities::o_auth2_sessions::{self, ActiveModel, Entity, Model};
use super::users;
use async_trait::async_trait;
use chrono::Local;
use loco_oauth2::{
base_oauth2::basic::BasicTokenResponse, base_oauth2::TokenResponse,
models::oauth2_sessions::OAuth2SessionsTrait,
};
use loco_rs::prelude::*;
use sea_orm::entity::prelude::*;
#[async_trait]
impl OAuth2SessionsTrait<users::Model> for Model {
/// Check if a session is expired from the database
///
/// # Arguments
/// db: &`DatabaseConnection` - Database connection
/// session_id: &str - Session id
/// # Returns
/// A boolean
/// # Errors
/// Returns a `ModelError` if the session is not found
async fn is_expired(db: &DatabaseConnection, session_id: &str) -> ModelResult<bool> {
let oauth2_session = o_auth2_sessions::Entity::find()
.filter(o_auth2_sessions::Column::SessionId.eq(session_id))
.one(db)
.await?
.ok_or_else(|| ModelError::EntityNotFound)?;
Ok(oauth2_session.expires_at < Local::now().naive_local())
}
/// Upsert a session with OAuth
///
/// # Arguments
/// db: &`DatabaseConnection` - Database connection
/// token: &`BasicTokenResponse` - OAuth token
/// user: &`users::Model` - User
/// # Returns
/// A session
/// # Errors
/// Returns a `ModelError` if the session cannot be upserted
async fn upsert_with_oauth2(
db: &DatabaseConnection,
token: &BasicTokenResponse,
user: &users::Model,
) -> ModelResult<Self> {
let txn = db.begin().await?;
let oauth2_session_id = token.access_token().secret().clone();
let oauth2_session = match o_auth2_sessions::Entity::find()
.filter(o_auth2_sessions::Column::UserId.eq(user.id))
.one(&txn)
.await?
{
Some(oauth2_session) => {
// Update the session
let mut oauth2_session: o_auth2_sessions::ActiveModel = oauth2_session.into();
oauth2_session.session_id = ActiveValue::set(oauth2_session_id);
oauth2_session.expires_at =
ActiveValue::set(Local::now().naive_local() + token.expires_in().unwrap());
oauth2_session.updated_at = ActiveValue::set(Local::now().naive_local());
oauth2_session.update(&txn).await?
}
None => {
// Create the session
o_auth2_sessions::ActiveModel {
session_id: ActiveValue::set(oauth2_session_id),
expires_at: ActiveValue::set(
Local::now().naive_local() + token.expires_in().unwrap(),
),
user_id: ActiveValue::set(user.id),
..Default::default()
}
.insert(&txn)
.await?
}
};
txn.commit().await?;
Ok(oauth2_session)
}
}
控制器
我们需要为OAuth2流程实现3个控制器。
authorization_url
- 此控制器用于获取用于将用户重定向到OAuth2提供者的授权URL。
callback
- 此控制器用于处理来自OAuth2提供者的回调。我们可以返回一个PrivateCookieJar
(它重定向到protected
路由)或一个JWT
令牌。
protected
- 此控制器用于保护路由免受未经授权的访问。
OAuth2Controller
示例
// src/controllers/oauth2.rs
use axum_session::SessionNullPool;
use loco_oauth2::controllers::oauth2::get_authorization_url;
use loco_oauth2::OAuth2ClientStore;
use crate::models::{o_auth2_sessions, users, users::OAuth2UserProfile};
/// The authorization URL for the `OAuth2` flow
/// This will redirect the user to the `OAuth2` provider's login page
/// and then to the callback URL
/// # Arguments
/// * `session` - The axum session
/// * `oauth_store` - The `OAuth2ClientStore` extension
/// # Returns
/// The HTML response with the link to the `OAuth2` provider's login page
/// # Errors
/// `loco_rs::errors::Error` - When the `OAuth2` client cannot be retrieved
pub async fn google_authorization_url(
session: Session<SessionNullPool>,
Extension(oauth2_store): Extension<OAuth2ClientStore>,
) -> Result<String> {
// Get the `google` Authorization Code Grant client from the `OAuth2ClientStore`
let mut client = oauth2_store
.get_authorization_code_client("google")
.await
.map_err(|e| {
tracing::error!("Error getting client: {:?}", e);
Error::InternalServerError
})?;
// Get the authorization URL and save the csrf token in the session
let auth_url = get_authorization_url(session, &mut client).await;
drop(client);
Ok(auth_url)
}
CallbackController Cookie
示例
use axum_session::SessionNullPool;
use loco_oauth2::controllers::oauth2::callback;
use loco_oauth2::OAuth2ClientStore;
use crate::models::{o_auth2_sessions, users, users::OAuth2UserProfile};
// src/controllers/oauth2.rs
/// The callback URL for the `OAuth2` flow
/// This will exchange the code for a token and then get the user profile
/// then upsert the user and the session and set the token in a short live
/// cookie Lastly, it will redirect the user to the protected URL
/// # Arguments
/// * `ctx` - The application context
/// * `session` - The axum session
/// * `params` - The query parameters
/// * `jar` - The oauth2 private cookie jar
/// * `oauth_store` - The `OAuth2ClientStore` extension
/// # Returns
/// The response with the short live cookie and the redirect to the protected
/// URL
/// # Errors
/// * `loco_rs::errors::Error`
pub async fn google_callback_cookie(
State(ctx): State<AppContext>,
session: Session<SessionNullPool>,
Query(params): Query<AuthParams>,
// Extract the private cookie jar from the request
jar: OAuth2PrivateCookieJar,
Extension(oauth2_store): Extension<OAuth2ClientStore>,
) -> Result<impl IntoResponse> {
let mut client = oauth2_store
.get_authorization_code_client("google")
.await
.map_err(|e| {
tracing::error!("Error getting client: {:?}", e);
Error::InternalServerError
})?;
// This function will validate the state from the callback. Then it will exchange the code for a token and then get the user profile. After that, the function will upsert the user and the session and set the token in a short live cookie and save the cookie in the private cookie jar. Lastly, the function will create a response with the short live cookie and the redirect to the protected URL
let response = callback::<OAuth2UserProfile, users::Model, o_auth2_sessions::Model, SessionNullPool, >(ctx, session, params, jar, &mut client).await?;
drop(client);
Ok(response)
}
CallbackController JWT
示例 - SPA应用
/// The callback URL for the `OAuth2` flow
/// This will exchange the code for a token and then get the user profile
/// then upsert the user and the session and set the token in a short live
/// cookie Lastly, it will redirect the user to the protected URL
/// # Generics
/// * `T` - The user profile, should implement `DeserializeOwned` and `Send`
/// * `U` - The user model, should implement `OAuth2UserTrait` and `ModelTrait`
/// * `V` - The session model, should implement `OAuth2SessionsTrait` and `ModelTrait`
/// * `W` - The database pool
/// # Arguments
/// * `ctx` - The application context
/// * `session` - The axum session
/// * `params` - The query parameters
/// * `oauth2_store` - The `OAuth2ClientStore` extension
/// # Return
/// * `Result<impl IntoResponse>` - The response with the jwt token
/// # Errors
/// * `loco_rs::errors::Error`
pub async fn google_callback_jwt(
State(ctx): State<AppContext>,
session: Session<SessionNullPool>,
Query(params): Query<AuthParams>,
Extension(oauth2_store): Extension<OAuth2ClientStore>,
) -> Result<impl IntoResponse> {
let mut client = oauth2_store
.get_authorization_code_client("google")
.await
.map_err(|e| {
tracing::error!("Error getting client: {:?}", e);
Error::InternalServerError
})?;
// Get JWT secret from the config
let jwt_secret = ctx.config.get_jwt_config()?;
let user = callback_jwt::<OAuth2UserProfile, users::Model, o_auth2_sessions::Model, SessionNullPool>(&ctx, session, params, &mut client).await?;
drop(client);
let token = user
.generate_jwt(&jwt_secret.secret, &jwt_secret.expiration)
.or_else(|_| unauthorized("unauthorized!"))?;
// Return jwt token
Ok(token)
}
ProtectedController
示例
// src/controllers/oauth2.rs
use loco_rs::prelude::*;
use crate::{
models::{o_auth2_sessions, users, users::OAuth2UserProfile},
views::auth::LoginResponse,
};
async fn protected(
State(ctx): State<AppContext>,
// Extract the user from the Cookie via middleware
user: OAuth2CookieUser<OAuth2UserProfile, users::Model, o_auth2_sessions::Model>,
) -> Result<Response> {
let user: &users::Model = user.as_ref();
let jwt_secret = ctx.config.get_jwt_config()?;
// Generate a JWT token
let token = user
.generate_jwt(&jwt_secret.secret, &jwt_secret.expiration)
.or_else(|_| unauthorized("unauthorized!"))?;
// Return the user and the token in JSON format
format::json(LoginResponse::new(user, &token))
}
Google 模板示例 - Cookie
由于我们使用Google OAuth2提供者,有一个Google模板代码来获取授权URL和回调Cookie。
// src/controllers/oauth2.rs
use axum_session::SessionNullPool;
use loco_oauth2::controllers::{
middleware::OAuth2CookieUser,
oauth2::{google_authorization_url, google_callback_cookie},
};
use loco_rs::prelude::*;
use crate::{
models::{o_auth2_sessions, users, users::OAuth2UserProfile},
views::auth::LoginResponse,
};
async fn protected(
State(ctx): State<AppContext>,
// Extract the user from the Cookie via middleware
user: OAuth2CookieUser<OAuth2UserProfile, users::Model, o_auth2_sessions::Model>,
) -> Result<Response> {
let user: &users::Model = user.as_ref();
let jwt_secret = ctx.config.get_jwt_config()?;
// Generate a JWT token
let token = user
.generate_jwt(&jwt_secret.secret, &jwt_secret.expiration)
.or_else(|_| unauthorized("unauthorized!"))?;
// Return the user and the token in JSON format
format::json(LoginResponse::new(user, &token))
}
pub fn routes() -> Routes {
Routes::new()
.prefix("api/oauth2")
.add("/google", get(google_authorization_url::<SessionNullPool>))
// Route for the Cookie callback
.add(
"/google/callback/cookie",
get(google_callback_cookie::<
OAuth2UserProfile,
users::Model,
o_auth2_sessions::Model,
SessionNullPool,
>),
)
.add("/protected", get(protected))
}
Google 模板示例 - JWT
由于我们使用Google OAuth2提供商,因此有一个用于获取授权URL和回调jwt的Google模板代码。
// src/controllers/oauth2.rs
use axum_session::SessionNullPool;
use loco_oauth2::controllers::{
oauth2::{google_authorization_url, google_callback_jwt},
};
use loco_rs::prelude::*;
use crate::{
models::{o_auth2_sessions, users, users::OAuth2UserProfile},
views::auth::LoginResponse,
};
pub fn routes() -> Routes {
Routes::new()
.prefix("api/oauth2")
.add("/google", get(google_authorization_url::<SessionNullPool>))
// Route for the JWT callback
.add(
"/google/callback/jwt",
get(google_callback_jwt::<
OAuth2UserProfile,
users::Model,
o_auth2_sessions::Model,
SessionNullPool,
>),
)
}
依赖项
~76–110MB
~2M SLoC