| 161 | } |
| 162 | |
| 163 | export class SelfManagedApiClient |
| 164 | implements IApiClientBase, ISelfManagedApiClient |
| 165 | { |
| 166 | #appConfig: Readonly<SelfManagedAppConfig>; |
| 167 | /** Resolved manager; read synchronously by the auth middleware and WebSocket config. */ |
| 168 | oidcManager?: MzOidcUserManager; |
| 169 | /** In-flight init promise; awaited by OidcProviderWrapper to gate rendering. */ |
| 170 | oidcManagerInitializationPromise?: Promise<MzOidcUserManager>; |
| 171 | authMode: SelfManagedAuthMode; |
| 172 | authApiBasePath: string; |
| 173 | mzHttpUrlScheme: HttpScheme; |
| 174 | mzWebsocketUrlScheme: WebsocketScheme; |
| 175 | mzApiFetch: Fetch; |
| 176 | getWsAuthConfig: () => MaterializeAuthConfig | null; |
| 177 | type = "self-managed" as const; |
| 178 | |
| 179 | #mzApiWithAuthRedirect = async (...req: Parameters<Fetch>) => { |
| 180 | const response = await globalFetch(...req); |
| 181 | if (response.status === 401) { |
| 182 | // Surface only what the server shares — results in a redirect when |
| 183 | // there is a 401 error. |
| 184 | const error = await readAuthErrorDetail(response); |
| 185 | await logoutAndRedirect({ apiClient: this, error }); |
| 186 | } |
| 187 | return response; |
| 188 | }; |
| 189 | |
| 190 | #oidcAuthMiddleware: Middleware = (next) => { |
| 191 | return async (...fetchArgs) => { |
| 192 | const [input, options = {}] = fetchArgs; |
| 193 | const idToken = this.oidcManager?.getIdToken(); |
| 194 | |
| 195 | const headers = copyHeaders(fetchArgs); |
| 196 | if (idToken) { |
| 197 | headers.set("Authorization", `Bearer ${idToken}`); |
| 198 | } |
| 199 | |
| 200 | const request = new Request(input, { ...options, headers }); |
| 201 | return next(request); |
| 202 | }; |
| 203 | }; |
| 204 | |
| 205 | // Mirrors getWsAuthConfig() so HTTP and websocket auth as the same role; |
| 206 | // otherwise HTTP falls back to anonymous_http_user and role-membership |
| 207 | // checks (e.g. pg_cancel_backend) reject it. |
| 208 | #flexibleDeploymentAuthMiddleware: Middleware = (next) => { |
| 209 | return async (...fetchArgs) => { |
| 210 | const [input, options = {}] = fetchArgs; |
| 211 | const headers = copyHeaders(fetchArgs); |
| 212 | const { user, password } = FLEXIBLE_DEPLOYMENT_USER; |
| 213 | headers.set("Authorization", `Basic ${btoa(`${user}:${password}`)}`); |
| 214 | const request = new Request(input, { ...options, headers }); |
| 215 | return next(request); |
| 216 | }; |
| 217 | }; |
| 218 | |
| 219 | constructor({ appConfig }: { appConfig: Readonly<SelfManagedAppConfig> }) { |
| 220 | this.#appConfig = appConfig; |
nothing calls this directly
no test coverage detected