1use crate::migrations::AUTH_OIDC_MIGRATIONS;
2use platform_core::AppContext;
3use platform_http::ApiOpenApiRouter;
4use platform_module::{
5 ConsoleNavigation, ConsoleNavigationGroup, ConsoleSurface, ConsoleSurfacePresentation,
6 ConsoleWorkspaceRef, HostLinkedModule, LinkedBinding, LinkedHttpContribution, Module,
7 ModuleHttpMethod, ModuleHttpRoute, ModuleManifest,
8};
9
10pub const MODULE_NAME: &str = "auth-oidc";
11const AUTH_PROVIDERS_READ: &str = "auth.providers.read";
12
13fn auth_workspace() -> ConsoleWorkspaceRef {
14 ConsoleWorkspaceRef {
15 id: "auth".to_owned(),
16 label: "Auth".to_owned(),
17 icon: Some("shield".to_owned()),
18 }
19}
20
21fn auth_sign_in_group() -> ConsoleNavigationGroup {
22 ConsoleNavigationGroup {
23 id: "sign-in".to_owned(),
24 label: "Sign-in".to_owned(),
25 icon: Some("git-compare-arrows".to_owned()),
26 order: Some(20),
27 }
28}
29
30pub fn http_routes() -> Vec<ModuleHttpRoute> {
31 vec![
32 ModuleHttpRoute {
33 method: ModuleHttpMethod::Get,
34 path: "/.well-known/openid-configuration".to_owned(),
35 capability: None,
36 operation: None,
37 display_name: Some("OIDC Provider Metadata".to_owned()),
38 story_title: Some("OIDC Discovery".to_owned()),
39 },
40 ModuleHttpRoute {
41 method: ModuleHttpMethod::Get,
42 path: "/.well-known/jwks.json".to_owned(),
43 capability: None,
44 operation: None,
45 display_name: Some("OIDC JSON Web Key Set".to_owned()),
46 story_title: Some("OIDC JWKS".to_owned()),
47 },
48 ModuleHttpRoute {
49 method: ModuleHttpMethod::Get,
50 path: "/oauth/authorize".to_owned(),
51 capability: None,
52 operation: None,
53 display_name: Some("OIDC Authorization".to_owned()),
54 story_title: Some("OIDC Authorization".to_owned()),
55 },
56 ModuleHttpRoute {
57 method: ModuleHttpMethod::Post,
58 path: "/oauth/token".to_owned(),
59 capability: None,
60 operation: None,
61 display_name: Some("OIDC Token Exchange".to_owned()),
62 story_title: Some("OIDC Token Exchange".to_owned()),
63 },
64 ]
65}
66
67pub fn console_surfaces() -> Vec<ConsoleSurface> {
68 vec![ConsoleSurface {
69 name: "oidc-provider".to_owned(),
70 label: "OIDC Provider".to_owned(),
71 route: "/auth/providers/oidc".to_owned(),
72 presentation: ConsoleSurfacePresentation::Esm {
73 entry: "oidc-provider".to_owned(),
74 },
75 icon: Some("settings".to_owned()),
76 required_capabilities: vec![AUTH_PROVIDERS_READ.to_owned()],
77 navigation: Some(ConsoleNavigation {
78 workspace: auth_workspace(),
79 group: Some(auth_sign_in_group()),
80 order: Some(83),
81 }),
82 }]
83}
84
85pub fn manifest() -> ModuleManifest {
86 ModuleManifest::builder(MODULE_NAME)
87 .dependencies(vec![auth::module::MODULE_NAME.to_owned()])
88 .capabilities(vec![AUTH_PROVIDERS_READ.to_owned()])
89 .http_routes(http_routes())
90 .console(console_surfaces())
91 .build()
92}
93
94pub fn merge_http(base: ApiOpenApiRouter) -> ApiOpenApiRouter {
95 base.merge(crate::routes::router())
96}
97
98pub fn binding() -> LinkedBinding {
99 LinkedBinding::builder()
100 .http(LinkedHttpContribution {
101 public_prefixes: &["/.well-known/", "/oauth/"],
102 merge: merge_http,
103 })
104 .build()
105}
106
107pub fn module(_ctx: &AppContext) -> Module {
108 Module::linked(manifest(), binding())
109}
110
111pub fn linked_module() -> HostLinkedModule {
112 HostLinkedModule::linked(MODULE_NAME, manifest, module, AUTH_OIDC_MIGRATIONS)
113}
114
115#[cfg(test)]
116mod tests {
117 use super::*;
118 use platform_module::{ModuleManifestLintSeverity, lint_module_manifest};
119
120 #[test]
121 fn manifest_declares_oidc_routes() {
122 let manifest = manifest();
123
124 assert_eq!(manifest.module_id, format!("lenso/{MODULE_NAME}"));
125 assert_eq!(manifest.http_routes, http_routes());
126 assert_eq!(manifest.console, console_surfaces());
127
128 let lints = lint_module_manifest(&manifest);
129 assert!(
130 lints
131 .iter()
132 .all(|lint| lint.severity == ModuleManifestLintSeverity::Ok),
133 "auth-oidc manifest should not have warning/error lints: {lints:?}"
134 );
135 }
136
137 #[test]
138 fn generated_console_manifest_matches_checked_in_artifact_manifest() {
139 let generated =
140 serde_json::to_value(manifest().console_module_manifest("^2.1.0", "^2.0.0"))
141 .expect("console module manifest should serialize");
142 let checked_in: serde_json::Value =
143 serde_json::from_str(include_str!("../console-module.json"))
144 .expect("console module manifest fixture should be valid JSON");
145
146 assert_eq!(generated, checked_in);
147 }
148}