1 个不稳定版本

0.2.0 2021 年 2 月 17 日

#32 in #cloudflare-workers

MIT/Apache 许可

50KB
822

wasm-service-oauth

使用 Cloudflare Workers 配合 OAuth

以下示例使用 Github 测试过。经过少量修改后,应与其他 OAuth 提供商兼容。

设置

配置参数通过一个 OAuthConfig 结构传递给服务进行初始化。以下示例在环境中设置了密钥参数,因此它们不是编译的 wasm 二进制文件的一部分。

  1. 创建一个包含 json 数据的环境变量 env_json,该数据将被工作者解析。

在 wrangler.toml 的底部添加以下内容。

[vars]

env_json= """{
  "oauth": {
    "app_url": "https://app.example.com/",
    "authorized_users": [ "gituser" ],
    "client_id" : "0000",
    "client_secret": "0000",
    "state_secret": "0000",
    "cors_origins": [ "https://app.example.com", "https://127.0.0.1:3000" ],
    "logged_out_app_url": "https://app.example.com/",
    "session_path_prefix": "/private/",
    "session_secret": "0000"
  }
}
  • app_url : 应用的基本 URL

  • client_idclient_secret:github API ID 和密钥

  • state_secretsession_secret:32 位加密密钥,以 64 个十六进制数字表示。在 Unix 上创建这些密钥的一种方法

    head--bytes32 /dev/urandom|hexdump-ve'1/1 "%.2x"' &&echo

  • logged_out_app_url:用户登出后被重定向到的位置

  • session_path_prefix:以该 URL 开头的任何 URL 都会设置其会话cookie

  1. 按以下方式更新您的服务程序
#[wasm_bindgen]
extern "C" {
    static env_json: String;
}

pub async fn main_entry(req: Jsvalue) -> Result<Jsvalue,JsValue> { 
    //  ...
    let environ_config = env_json.as_str();
    let settings = load(environ_config).map_err(|e| JsValue::from_str(&e))?;
    // ...

    let oauth_config = build_oauth_config(&settings.oauth)?;
    let oauth_handler = OAuthHandler::init(oauth_config)
        .map_err(|e| JsValue::from(&format!("OAuthHandler init error: {}", e.to_string())))?;
    
    
    wasm_service::service_request(
        req,
        ServiceConfig {
            logger, 
            handlers: vec![
                Box::new(MyHandler(oauth_handler))
            ],
            ..Default::default()
        }
    ).await
}

fn build_oauth_config(env: &Oauth) -> Result<OAuthConfig, JsValue> {
    let allow = wasm_service_oauth::UserAllowList {
        allowed_users: env.authorized_users.clone(),
        login_failed_url: "/login-failed".into(),
    };

    let config = OAuthConfig {
        app_url: env.app_url.to_string(),
        logged_out_app_url: env.logged_out_app_url.to_string(),
        authorize_url_path: "/authorize".to_string(),
        code_url_path: "/code".to_string(),
        login_failed_url_path: "/login-failed".to_string(),
        logout_url_path: "/logout".to_string(),
        auth_checker: Box::new(allow),
        client_id: env.client_id.to_string(),
        client_secret: env.client_secret.to_string(),
        state_secret: key_from_hex(&env.state_secret, 32).map_err(JsValue::from)?,
        session_secret: key_from_hex(&env.session_secret, 32).map_err(JsValue::from)?,
        session_cookie_path_prefix: env.session_path_prefix.to_string(),
        cors_origins: env.cors_origins.clone(), // .iter().map(|v| v.as_ref()).collect(),
        ..Default::default()
    };
    Ok(config)
}

/// load config from environment
pub(crate) fn load(json: &str) -> Result<Config, String> {
    //let var = std::env::var("env_json")
    //    .map_err(|_| Error::Environment("Missing env_json".to_string()))?;
    let conf = serde_json::from_str(json).map_err(|e| e.to_string())?;
    Ok(conf)
}

#[derive(Debug, Deserialize)]
pub struct Config {
    pub oauth: Oauth,
}
#[derive(Debug, Deserialize)]
pub struct Oauth {
    pub client_id: String,
    pub client_secret: String,
    pub state_secret: String,
    pub session_secret: String,
    pub session_path_prefix: String,
    pub app_url: String,
    pub logged_out_app_url: String,
    pub cors_origins: Vec<String>,
    pub authorized_users: Vec<String>,
}
  1. 按以下方式更新处理函数
async fn handle(&self, req: &Request, mut ctx: &mut Context) -> Result<(), HandlerReturn> {
    
    // urls beginning with session_path_prefix require authentication
    if req.url().path().starts_with("/private/") {
        let _session = self.oauth_handler.verify_auth_user(req, &mut ctx)?;
        // user is authenticated!!
        // ...

    } else {
        // handle urls not requiring authentication
        // ...
    }

    // let oauth handler process its urls
    if ctx.response().is_unset() {
        if self.oauth_handler.would_handle(&req) {
            // handle oauth processing for /code, /authorize, /login-failed, etc.
            self.oauth_handler.handle(req, &mut ctx).await?;
        }
    }
    Ok(())
}

依赖项

~12–32MB
~567K SLoC