Skip to main content

auth_password/
module.rs

1use crate::config::{AuthPasswordConfig, TokenStrategy};
2use platform_core::AppContext;
3use platform_http::ApiOpenApiRouter;
4use platform_module::{
5    LinkedBinding, LinkedHttpContribution, Module, ModuleHttpMethod, ModuleHttpRoute,
6    ModuleManifest,
7};
8
9pub const MODULE_NAME: &str = "auth-password";
10pub const AUTH_MODULE_DEPENDENCY: &str = "auth";
11
12pub fn http_routes() -> Vec<ModuleHttpRoute> {
13    vec![
14        ModuleHttpRoute {
15            method: ModuleHttpMethod::Post,
16            path: "/v1/auth/password/register".to_owned(),
17            capability: None,
18            display_name: Some("Register With Password".to_owned()),
19            story_title: Some("Password Registration".to_owned()),
20        },
21        ModuleHttpRoute {
22            method: ModuleHttpMethod::Post,
23            path: "/v1/auth/password/login".to_owned(),
24            capability: None,
25            display_name: Some("Login With Password".to_owned()),
26            story_title: Some("Password Login".to_owned()),
27        },
28    ]
29}
30
31pub fn manifest() -> ModuleManifest {
32    ModuleManifest::builder(MODULE_NAME)
33        .dependencies(vec![AUTH_MODULE_DEPENDENCY.to_owned()])
34        .http_routes(http_routes())
35        .build()
36}
37
38pub fn merge_http(base: ApiOpenApiRouter) -> ApiOpenApiRouter {
39    base.merge(crate::routes::router())
40}
41
42pub fn binding() -> LinkedBinding {
43    LinkedBinding::builder()
44        .http(LinkedHttpContribution {
45            public_prefixes: &["/v1/auth/password/"],
46            merge: merge_http,
47        })
48        .build()
49}
50
51pub fn module(_ctx: &AppContext) -> Module {
52    Module::linked(manifest(), binding())
53        .with_runtime_config_groups(crate::config::RUNTIME_CONFIG_GROUPS.as_slice())
54        .with_runtime_config(crate::config::RUNTIME_CONFIG.as_slice())
55}
56
57/// Build a [`JwtActorResolver`] if auth-password is configured with `token_strategy = "jwt"`.
58///
59/// Returns `Ok(None)` when the strategy is `"session"` (the default) or when
60/// the auth-password module is not enabled. Returns `Ok(Some(..))` with the
61/// resolver wrapping the given `fallback`.
62pub fn jwt_actor_resolver(
63    ctx: &AppContext,
64    fallback: std::sync::Arc<dyn platform_core::ActorResolver>,
65) -> platform_core::AppResult<Option<std::sync::Arc<dyn platform_core::ActorResolver>>> {
66    let config = AuthPasswordConfig::from_context(ctx)?;
67    if config.token_strategy == TokenStrategy::Jwt && config.jwt_secret.is_none() {
68        return Ok(None);
69    }
70    let jwt_config = match config.jwt_config()? {
71        Some(cfg) => cfg,
72        None => return Ok(None),
73    };
74    Ok(Some(std::sync::Arc::new(
75        crate::resolver::JwtActorResolver::new(jwt_config, fallback),
76    )))
77}
78
79#[cfg(test)]
80mod tests {
81    use super::*;
82    use platform_module::{ModuleManifestLintSeverity, ModuleSource, lint_module_manifest};
83
84    #[test]
85    fn manifest_declares_password_routes() {
86        let manifest = manifest();
87
88        assert_eq!(manifest.name, MODULE_NAME);
89        assert_eq!(manifest.http_routes, http_routes());
90
91        let lints = lint_module_manifest(ModuleSource::Linked, &manifest);
92        assert!(
93            lints
94                .iter()
95                .all(|lint| lint.severity == ModuleManifestLintSeverity::Ok),
96            "auth-password manifest should not have warning/error lints: {lints:?}"
97        );
98    }
99}