lenso-module-auth-oidc 0.1.7

First-party OIDC provider module for the Lenso backend framework.
Documentation
use crate::migrations::AUTH_OIDC_MIGRATIONS;
use platform_core::AppContext;
use platform_http::ApiOpenApiRouter;
use platform_module::{
    ConsoleNavigation, ConsoleSurface, ConsoleSurfacePresentation, ConsoleWorkspaceRef,
    HostLinkedModule, LinkedBinding, LinkedHttpContribution, Module, ModuleHttpMethod,
    ModuleHttpRoute, ModuleManifest,
};

pub const MODULE_NAME: &str = "auth-oidc";
const AUTH_PROVIDERS_READ: &str = "auth.providers.read";

fn auth_workspace() -> ConsoleWorkspaceRef {
    ConsoleWorkspaceRef {
        id: "auth".to_owned(),
        label: "Auth".to_owned(),
        icon: Some("shield".to_owned()),
    }
}

pub fn http_routes() -> Vec<ModuleHttpRoute> {
    vec![
        ModuleHttpRoute {
            method: ModuleHttpMethod::Get,
            path: "/.well-known/openid-configuration".to_owned(),
            capability: None,
            operation: None,
            display_name: Some("OIDC Provider Metadata".to_owned()),
            story_title: Some("OIDC Discovery".to_owned()),
        },
        ModuleHttpRoute {
            method: ModuleHttpMethod::Get,
            path: "/.well-known/jwks.json".to_owned(),
            capability: None,
            operation: None,
            display_name: Some("OIDC JSON Web Key Set".to_owned()),
            story_title: Some("OIDC JWKS".to_owned()),
        },
        ModuleHttpRoute {
            method: ModuleHttpMethod::Get,
            path: "/oauth/authorize".to_owned(),
            capability: None,
            operation: None,
            display_name: Some("OIDC Authorization".to_owned()),
            story_title: Some("OIDC Authorization".to_owned()),
        },
        ModuleHttpRoute {
            method: ModuleHttpMethod::Post,
            path: "/oauth/token".to_owned(),
            capability: None,
            operation: None,
            display_name: Some("OIDC Token Exchange".to_owned()),
            story_title: Some("OIDC Token Exchange".to_owned()),
        },
    ]
}

pub fn console_surfaces() -> Vec<ConsoleSurface> {
    vec![ConsoleSurface {
        name: "oidc-provider".to_owned(),
        label: "OIDC Provider".to_owned(),
        route: "/data/auth/providers/oidc".to_owned(),
        presentation: ConsoleSurfacePresentation::Esm {
            entry: "oidc-provider".to_owned(),
        },
        icon: Some("shield".to_owned()),
        required_capabilities: vec![AUTH_PROVIDERS_READ.to_owned()],
        navigation: Some(ConsoleNavigation {
            workspace: auth_workspace(),
            group: None,
            order: Some(83),
        }),
    }]
}

pub fn manifest() -> ModuleManifest {
    ModuleManifest::builder(MODULE_NAME)
        .dependencies(vec![auth::module::MODULE_NAME.to_owned()])
        .capabilities(vec![AUTH_PROVIDERS_READ.to_owned()])
        .http_routes(http_routes())
        .console(console_surfaces())
        .build()
}

pub fn merge_http(base: ApiOpenApiRouter) -> ApiOpenApiRouter {
    base.merge(crate::routes::router())
}

pub fn binding() -> LinkedBinding {
    LinkedBinding::builder()
        .http(LinkedHttpContribution {
            public_prefixes: &["/.well-known/", "/oauth/"],
            merge: merge_http,
        })
        .build()
}

pub fn module(_ctx: &AppContext) -> Module {
    Module::linked(manifest(), binding())
}

pub fn linked_module() -> HostLinkedModule {
    HostLinkedModule::linked(MODULE_NAME, manifest, module, AUTH_OIDC_MIGRATIONS)
}

#[cfg(test)]
mod tests {
    use super::*;
    use platform_module::{ModuleManifestLintSeverity, lint_module_manifest};

    #[test]
    fn manifest_declares_oidc_routes() {
        let manifest = manifest();

        assert_eq!(manifest.module_id, format!("lenso/{MODULE_NAME}"));
        assert_eq!(manifest.http_routes, http_routes());
        assert_eq!(manifest.console, console_surfaces());

        let lints = lint_module_manifest(&manifest);
        assert!(
            lints
                .iter()
                .all(|lint| lint.severity == ModuleManifestLintSeverity::Ok),
            "auth-oidc manifest should not have warning/error lints: {lints:?}"
        );
    }

    #[test]
    fn generated_console_manifest_matches_checked_in_artifact_manifest() {
        let generated =
            serde_json::to_value(manifest().console_module_manifest("^1.0.0", "^2.0.0"))
                .expect("console module manifest should serialize");
        let checked_in: serde_json::Value =
            serde_json::from_str(include_str!("../console-module.json"))
                .expect("console module manifest fixture should be valid JSON");

        assert_eq!(generated, checked_in);
    }
}