diff --git a/frontend/src/api/generated/.openapi-generator/FILES b/frontend/src/api/generated/.openapi-generator/FILES
index cac2f51..af12e66 100644
--- a/frontend/src/api/generated/.openapi-generator/FILES
+++ b/frontend/src/api/generated/.openapi-generator/FILES
@@ -30,6 +30,10 @@ models/LoginOtp500Response.ts
models/LoginOtpRequest.ts
models/LoginRequest.ts
models/Logout200Response.ts
+models/ProviderList200Response.ts
+models/ProviderList200ResponsePayload.ts
+models/ProviderList200ResponsePayloadListInner.ts
+models/ProviderList200ResponsePayloadListInnerColors.ts
models/Signin200Response.ts
models/Signin200ResponsePayload.ts
models/Signin400Response.ts
diff --git a/frontend/src/api/generated/apis/OpenapiOtherApi.ts b/frontend/src/api/generated/apis/OpenapiOtherApi.ts
index aba283f..fda4f60 100644
--- a/frontend/src/api/generated/apis/OpenapiOtherApi.ts
+++ b/frontend/src/api/generated/apis/OpenapiOtherApi.ts
@@ -38,6 +38,7 @@ import type {
LoginOtpRequest,
LoginRequest,
Logout200Response,
+ ProviderList200Response,
Signin200Response,
Signin400Response,
Signin500Response,
@@ -92,6 +93,8 @@ import {
LoginRequestToJSON,
Logout200ResponseFromJSON,
Logout200ResponseToJSON,
+ ProviderList200ResponseFromJSON,
+ ProviderList200ResponseToJSON,
Signin200ResponseFromJSON,
Signin200ResponseToJSON,
Signin400ResponseFromJSON,
@@ -515,6 +518,44 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
return await response.value();
}
+ /**
+ */
+ async providerListRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> {
+ const queryParameters: any = {};
+
+ const headerParameters: runtime.HTTPHeaders = {};
+
+
+ let urlPath = `/api/auth/providerList`;
+
+ const response = await this.request({
+ path: urlPath,
+ method: 'GET',
+ headers: headerParameters,
+ query: queryParameters,
+ }, initOverrides);
+
+ // CHANGED: Handle all status codes defined in the OpenAPI spec, not just 2xx responses
+ // This allows typed access to error responses (4xx, 5xx) and other status codes.
+ // The code routes responses based on the actual HTTP status code and returns
+ // appropriately typed ApiResponse wrappers for each status code.
+ if (response.status === 200) {
+ // Object response for status 200
+ return new runtime.JSONApiResponse(response, (jsonValue) => ProviderList200ResponseFromJSON(jsonValue));
+ }
+ // CHANGED: Throw error if status code is not handled by any of the defined responses
+ // This ensures all code paths return a value and provides clear error messages for unexpected status codes
+ // Only throw if responses were defined but none matched the actual status code
+ throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200`);
+ }
+
+ /**
+ */
+ async providerList(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise {
+ const response = await this.providerListRaw(initOverrides);
+ return await response.value();
+ }
+
/**
*/
async signinRaw(requestParameters: SigninRequest, initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise> {
diff --git a/frontend/src/api/generated/docs/OpenapiOtherApi.md b/frontend/src/api/generated/docs/OpenapiOtherApi.md
new file mode 100644
index 0000000..0ebde50
--- /dev/null
+++ b/frontend/src/api/generated/docs/OpenapiOtherApi.md
@@ -0,0 +1,637 @@
+# OpenapiOtherApi
+
+All URIs are relative to *https://local.maix.me:8888*
+
+| Method | HTTP request | Description |
+|------------- | ------------- | -------------|
+| [**disableOtp**](OpenapiOtherApi.md#disableotp) | **PUT** /api/auth/disableOtp | |
+| [**enableOtp**](OpenapiOtherApi.md#enableotp) | **PUT** /api/auth/enableOtp | |
+| [**getUser**](OpenapiOtherApi.md#getuser) | **GET** /api/user/info/{user} | |
+| [**guestLogin**](OpenapiOtherApi.md#guestlogin) | **POST** /api/auth/guest | |
+| [**login**](OpenapiOtherApi.md#loginoperation) | **POST** /api/auth/login | |
+| [**loginOtp**](OpenapiOtherApi.md#loginotpoperation) | **POST** /api/auth/otp | |
+| [**logout**](OpenapiOtherApi.md#logout) | **POST** /api/auth/logout | |
+| [**providerList**](OpenapiOtherApi.md#providerlist) | **GET** /api/auth/providerList | |
+| [**signin**](OpenapiOtherApi.md#signin) | **POST** /api/auth/signin | |
+| [**statusOtp**](OpenapiOtherApi.md#statusotp) | **GET** /api/auth/statusOtp | |
+
+
+
+## disableOtp
+
+> DisableOtp200Response disableOtp()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { DisableOtpRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.disableOtp();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**DisableOtp200Response**](DisableOtp200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **401** | Default Response | - |
+| **500** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## enableOtp
+
+> EnableOtp200Response enableOtp()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { EnableOtpRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.enableOtp();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**EnableOtp200Response**](EnableOtp200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **401** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## getUser
+
+> GetUser200Response getUser(user)
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { GetUserRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ const body = {
+ // GetUserUserParameter
+ user: ...,
+ } satisfies GetUserRequest;
+
+ try {
+ const data = await api.getUser(body);
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+
+| Name | Type | Description | Notes |
+|------------- | ------------- | ------------- | -------------|
+| **user** | [](.md) | | [Defaults to `undefined`] |
+
+### Return type
+
+[**GetUser200Response**](GetUser200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **401** | Default Response | - |
+| **403** | Default Response | - |
+| **404** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## guestLogin
+
+> GuestLogin200Response guestLogin()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { GuestLoginRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.guestLogin();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**GuestLogin200Response**](GuestLogin200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **500** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## login
+
+> Login200Response login(loginRequest)
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { LoginOperationRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ const body = {
+ // LoginRequest
+ loginRequest: ...,
+ } satisfies LoginOperationRequest;
+
+ try {
+ const data = await api.login(body);
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+
+| Name | Type | Description | Notes |
+|------------- | ------------- | ------------- | -------------|
+| **loginRequest** | [LoginRequest](LoginRequest.md) | | |
+
+### Return type
+
+[**Login200Response**](Login200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: `application/json`
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **202** | Default Response | - |
+| **400** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## loginOtp
+
+> LoginOtp200Response loginOtp(loginOtpRequest)
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { LoginOtpOperationRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ const body = {
+ // LoginOtpRequest
+ loginOtpRequest: ...,
+ } satisfies LoginOtpOperationRequest;
+
+ try {
+ const data = await api.loginOtp(body);
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+
+| Name | Type | Description | Notes |
+|------------- | ------------- | ------------- | -------------|
+| **loginOtpRequest** | [LoginOtpRequest](LoginOtpRequest.md) | | |
+
+### Return type
+
+[**LoginOtp200Response**](LoginOtp200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: `application/json`
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **400** | Default Response | - |
+| **401** | Default Response | - |
+| **408** | Default Response | - |
+| **500** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## logout
+
+> Logout200Response logout()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { LogoutRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.logout();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**Logout200Response**](Logout200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## providerList
+
+> ProviderList200Response providerList()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { ProviderListRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.providerList();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**ProviderList200Response**](ProviderList200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## signin
+
+> Signin200Response signin(loginRequest)
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { SigninRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ const body = {
+ // LoginRequest
+ loginRequest: ...,
+ } satisfies SigninRequest;
+
+ try {
+ const data = await api.signin(body);
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+
+| Name | Type | Description | Notes |
+|------------- | ------------- | ------------- | -------------|
+| **loginRequest** | [LoginRequest](LoginRequest.md) | | |
+
+### Return type
+
+[**Signin200Response**](Signin200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: `application/json`
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **400** | Default Response | - |
+| **500** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
+## statusOtp
+
+> StatusOtp200Response statusOtp()
+
+
+
+### Example
+
+```ts
+import {
+ Configuration,
+ OpenapiOtherApi,
+} from '';
+import type { StatusOtpRequest } from '';
+
+async function example() {
+ console.log("🚀 Testing SDK...");
+ const api = new OpenapiOtherApi();
+
+ try {
+ const data = await api.statusOtp();
+ console.log(data);
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+// Run the test
+example().catch(console.error);
+```
+
+### Parameters
+
+This endpoint does not need any parameter.
+
+### Return type
+
+[**StatusOtp200Response**](StatusOtp200Response.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+- **Content-Type**: Not defined
+- **Accept**: `application/json`
+
+
+### HTTP response details
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+| **200** | Default Response | - |
+| **401** | Default Response | - |
+| **500** | Default Response | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
diff --git a/frontend/src/api/generated/docs/ProviderList200Response.md b/frontend/src/api/generated/docs/ProviderList200Response.md
new file mode 100644
index 0000000..717edee
--- /dev/null
+++ b/frontend/src/api/generated/docs/ProviderList200Response.md
@@ -0,0 +1,38 @@
+
+# ProviderList200Response
+
+
+## Properties
+
+Name | Type
+------------ | -------------
+`kind` | string
+`msg` | string
+`payload` | [ProviderList200ResponsePayload](ProviderList200ResponsePayload.md)
+
+## Example
+
+```typescript
+import type { ProviderList200Response } from ''
+
+// TODO: Update the object below with actual values
+const example = {
+ "kind": null,
+ "msg": null,
+ "payload": null,
+} satisfies ProviderList200Response
+
+console.log(example)
+
+// Convert the instance to a JSON string
+const exampleJSON: string = JSON.stringify(example)
+console.log(exampleJSON)
+
+// Parse the JSON string back to an object
+const exampleParsed = JSON.parse(exampleJSON) as ProviderList200Response
+console.log(exampleParsed)
+```
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
diff --git a/frontend/src/api/generated/docs/ProviderList200ResponsePayload.md b/frontend/src/api/generated/docs/ProviderList200ResponsePayload.md
new file mode 100644
index 0000000..7b16838
--- /dev/null
+++ b/frontend/src/api/generated/docs/ProviderList200ResponsePayload.md
@@ -0,0 +1,34 @@
+
+# ProviderList200ResponsePayload
+
+
+## Properties
+
+Name | Type
+------------ | -------------
+`list` | [Array<ProviderList200ResponsePayloadListInner>](ProviderList200ResponsePayloadListInner.md)
+
+## Example
+
+```typescript
+import type { ProviderList200ResponsePayload } from ''
+
+// TODO: Update the object below with actual values
+const example = {
+ "list": null,
+} satisfies ProviderList200ResponsePayload
+
+console.log(example)
+
+// Convert the instance to a JSON string
+const exampleJSON: string = JSON.stringify(example)
+console.log(exampleJSON)
+
+// Parse the JSON string back to an object
+const exampleParsed = JSON.parse(exampleJSON) as ProviderList200ResponsePayload
+console.log(exampleParsed)
+```
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
diff --git a/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInner.md b/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInner.md
new file mode 100644
index 0000000..0d037e4
--- /dev/null
+++ b/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInner.md
@@ -0,0 +1,38 @@
+
+# ProviderList200ResponsePayloadListInner
+
+
+## Properties
+
+Name | Type
+------------ | -------------
+`displayName` | string
+`name` | string
+`colors` | [ProviderList200ResponsePayloadListInnerColors](ProviderList200ResponsePayloadListInnerColors.md)
+
+## Example
+
+```typescript
+import type { ProviderList200ResponsePayloadListInner } from ''
+
+// TODO: Update the object below with actual values
+const example = {
+ "displayName": null,
+ "name": null,
+ "colors": null,
+} satisfies ProviderList200ResponsePayloadListInner
+
+console.log(example)
+
+// Convert the instance to a JSON string
+const exampleJSON: string = JSON.stringify(example)
+console.log(exampleJSON)
+
+// Parse the JSON string back to an object
+const exampleParsed = JSON.parse(exampleJSON) as ProviderList200ResponsePayloadListInner
+console.log(exampleParsed)
+```
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
diff --git a/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInnerColors.md b/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInnerColors.md
new file mode 100644
index 0000000..4a62bef
--- /dev/null
+++ b/frontend/src/api/generated/docs/ProviderList200ResponsePayloadListInnerColors.md
@@ -0,0 +1,36 @@
+
+# ProviderList200ResponsePayloadListInnerColors
+
+
+## Properties
+
+Name | Type
+------------ | -------------
+`normal` | string
+`hover` | string
+
+## Example
+
+```typescript
+import type { ProviderList200ResponsePayloadListInnerColors } from ''
+
+// TODO: Update the object below with actual values
+const example = {
+ "normal": null,
+ "hover": null,
+} satisfies ProviderList200ResponsePayloadListInnerColors
+
+console.log(example)
+
+// Convert the instance to a JSON string
+const exampleJSON: string = JSON.stringify(example)
+console.log(exampleJSON)
+
+// Parse the JSON string back to an object
+const exampleParsed = JSON.parse(exampleJSON) as ProviderList200ResponsePayloadListInnerColors
+console.log(exampleParsed)
+```
+
+[[Back to top]](#) [[Back to API list]](../README.md#api-endpoints) [[Back to Model list]](../README.md#models) [[Back to README]](../README.md)
+
+
diff --git a/frontend/src/api/generated/models/ProviderList200Response.ts b/frontend/src/api/generated/models/ProviderList200Response.ts
new file mode 100644
index 0000000..8f13be8
--- /dev/null
+++ b/frontend/src/api/generated/models/ProviderList200Response.ts
@@ -0,0 +1,110 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * @fastify/swagger
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 9.6.1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { mapValues } from '../runtime';
+import type { ProviderList200ResponsePayload } from './ProviderList200ResponsePayload';
+import {
+ ProviderList200ResponsePayloadFromJSON,
+ ProviderList200ResponsePayloadFromJSONTyped,
+ ProviderList200ResponsePayloadToJSON,
+ ProviderList200ResponsePayloadToJSONTyped,
+} from './ProviderList200ResponsePayload';
+
+/**
+ *
+ * @export
+ * @interface ProviderList200Response
+ */
+export interface ProviderList200Response {
+ /**
+ *
+ * @type {string}
+ * @memberof ProviderList200Response
+ */
+ kind: ProviderList200ResponseKindEnum;
+ /**
+ *
+ * @type {string}
+ * @memberof ProviderList200Response
+ */
+ msg: ProviderList200ResponseMsgEnum;
+ /**
+ *
+ * @type {ProviderList200ResponsePayload}
+ * @memberof ProviderList200Response
+ */
+ payload: ProviderList200ResponsePayload;
+}
+
+
+/**
+ * @export
+ */
+export const ProviderList200ResponseKindEnum = {
+ Success: 'success'
+} as const;
+export type ProviderList200ResponseKindEnum = typeof ProviderList200ResponseKindEnum[keyof typeof ProviderList200ResponseKindEnum];
+
+/**
+ * @export
+ */
+export const ProviderList200ResponseMsgEnum = {
+ ProviderListSuccess: 'providerList.success'
+} as const;
+export type ProviderList200ResponseMsgEnum = typeof ProviderList200ResponseMsgEnum[keyof typeof ProviderList200ResponseMsgEnum];
+
+
+/**
+ * Check if a given object implements the ProviderList200Response interface.
+ */
+export function instanceOfProviderList200Response(value: object): value is ProviderList200Response {
+ if (!('kind' in value) || value['kind'] === undefined) return false;
+ if (!('msg' in value) || value['msg'] === undefined) return false;
+ if (!('payload' in value) || value['payload'] === undefined) return false;
+ return true;
+}
+
+export function ProviderList200ResponseFromJSON(json: any): ProviderList200Response {
+ return ProviderList200ResponseFromJSONTyped(json, false);
+}
+
+export function ProviderList200ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): ProviderList200Response {
+ if (json == null) {
+ return json;
+ }
+ return {
+
+ 'kind': json['kind'],
+ 'msg': json['msg'],
+ 'payload': ProviderList200ResponsePayloadFromJSON(json['payload']),
+ };
+}
+
+export function ProviderList200ResponseToJSON(json: any): ProviderList200Response {
+ return ProviderList200ResponseToJSONTyped(json, false);
+}
+
+export function ProviderList200ResponseToJSONTyped(value?: ProviderList200Response | null, ignoreDiscriminator: boolean = false): any {
+ if (value == null) {
+ return value;
+ }
+
+ return {
+
+ 'kind': value['kind'],
+ 'msg': value['msg'],
+ 'payload': ProviderList200ResponsePayloadToJSON(value['payload']),
+ };
+}
+
diff --git a/frontend/src/api/generated/models/ProviderList200ResponsePayload.ts b/frontend/src/api/generated/models/ProviderList200ResponsePayload.ts
new file mode 100644
index 0000000..aa6c94f
--- /dev/null
+++ b/frontend/src/api/generated/models/ProviderList200ResponsePayload.ts
@@ -0,0 +1,74 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * @fastify/swagger
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 9.6.1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { mapValues } from '../runtime';
+import type { ProviderList200ResponsePayloadListInner } from './ProviderList200ResponsePayloadListInner';
+import {
+ ProviderList200ResponsePayloadListInnerFromJSON,
+ ProviderList200ResponsePayloadListInnerFromJSONTyped,
+ ProviderList200ResponsePayloadListInnerToJSON,
+ ProviderList200ResponsePayloadListInnerToJSONTyped,
+} from './ProviderList200ResponsePayloadListInner';
+
+/**
+ *
+ * @export
+ * @interface ProviderList200ResponsePayload
+ */
+export interface ProviderList200ResponsePayload {
+ /**
+ *
+ * @type {Array}
+ * @memberof ProviderList200ResponsePayload
+ */
+ list: Array;
+}
+
+/**
+ * Check if a given object implements the ProviderList200ResponsePayload interface.
+ */
+export function instanceOfProviderList200ResponsePayload(value: object): value is ProviderList200ResponsePayload {
+ if (!('list' in value) || value['list'] === undefined) return false;
+ return true;
+}
+
+export function ProviderList200ResponsePayloadFromJSON(json: any): ProviderList200ResponsePayload {
+ return ProviderList200ResponsePayloadFromJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): ProviderList200ResponsePayload {
+ if (json == null) {
+ return json;
+ }
+ return {
+
+ 'list': ((json['list'] as Array).map(ProviderList200ResponsePayloadListInnerFromJSON)),
+ };
+}
+
+export function ProviderList200ResponsePayloadToJSON(json: any): ProviderList200ResponsePayload {
+ return ProviderList200ResponsePayloadToJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadToJSONTyped(value?: ProviderList200ResponsePayload | null, ignoreDiscriminator: boolean = false): any {
+ if (value == null) {
+ return value;
+ }
+
+ return {
+
+ 'list': ((value['list'] as Array).map(ProviderList200ResponsePayloadListInnerToJSON)),
+ };
+}
+
diff --git a/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInner.ts b/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInner.ts
new file mode 100644
index 0000000..7b5a8c0
--- /dev/null
+++ b/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInner.ts
@@ -0,0 +1,92 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * @fastify/swagger
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 9.6.1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { mapValues } from '../runtime';
+import type { ProviderList200ResponsePayloadListInnerColors } from './ProviderList200ResponsePayloadListInnerColors';
+import {
+ ProviderList200ResponsePayloadListInnerColorsFromJSON,
+ ProviderList200ResponsePayloadListInnerColorsFromJSONTyped,
+ ProviderList200ResponsePayloadListInnerColorsToJSON,
+ ProviderList200ResponsePayloadListInnerColorsToJSONTyped,
+} from './ProviderList200ResponsePayloadListInnerColors';
+
+/**
+ *
+ * @export
+ * @interface ProviderList200ResponsePayloadListInner
+ */
+export interface ProviderList200ResponsePayloadListInner {
+ /**
+ * Name to display to the user
+ * @type {string}
+ * @memberof ProviderList200ResponsePayloadListInner
+ */
+ displayName: string;
+ /**
+ * internal Name of the provider
+ * @type {string}
+ * @memberof ProviderList200ResponsePayloadListInner
+ */
+ name: string;
+ /**
+ *
+ * @type {ProviderList200ResponsePayloadListInnerColors}
+ * @memberof ProviderList200ResponsePayloadListInner
+ */
+ colors: ProviderList200ResponsePayloadListInnerColors;
+}
+
+/**
+ * Check if a given object implements the ProviderList200ResponsePayloadListInner interface.
+ */
+export function instanceOfProviderList200ResponsePayloadListInner(value: object): value is ProviderList200ResponsePayloadListInner {
+ if (!('displayName' in value) || value['displayName'] === undefined) return false;
+ if (!('name' in value) || value['name'] === undefined) return false;
+ if (!('colors' in value) || value['colors'] === undefined) return false;
+ return true;
+}
+
+export function ProviderList200ResponsePayloadListInnerFromJSON(json: any): ProviderList200ResponsePayloadListInner {
+ return ProviderList200ResponsePayloadListInnerFromJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadListInnerFromJSONTyped(json: any, ignoreDiscriminator: boolean): ProviderList200ResponsePayloadListInner {
+ if (json == null) {
+ return json;
+ }
+ return {
+
+ 'displayName': json['display_name'],
+ 'name': json['name'],
+ 'colors': ProviderList200ResponsePayloadListInnerColorsFromJSON(json['colors']),
+ };
+}
+
+export function ProviderList200ResponsePayloadListInnerToJSON(json: any): ProviderList200ResponsePayloadListInner {
+ return ProviderList200ResponsePayloadListInnerToJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadListInnerToJSONTyped(value?: ProviderList200ResponsePayloadListInner | null, ignoreDiscriminator: boolean = false): any {
+ if (value == null) {
+ return value;
+ }
+
+ return {
+
+ 'display_name': value['displayName'],
+ 'name': value['name'],
+ 'colors': ProviderList200ResponsePayloadListInnerColorsToJSON(value['colors']),
+ };
+}
+
diff --git a/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInnerColors.ts b/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInnerColors.ts
new file mode 100644
index 0000000..7a3f237
--- /dev/null
+++ b/frontend/src/api/generated/models/ProviderList200ResponsePayloadListInnerColors.ts
@@ -0,0 +1,75 @@
+/* tslint:disable */
+/* eslint-disable */
+/**
+ * @fastify/swagger
+ * No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
+ *
+ * The version of the OpenAPI document: 9.6.1
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ */
+
+import { mapValues } from '../runtime';
+/**
+ *
+ * @export
+ * @interface ProviderList200ResponsePayloadListInnerColors
+ */
+export interface ProviderList200ResponsePayloadListInnerColors {
+ /**
+ * Default color for the provider
+ * @type {string}
+ * @memberof ProviderList200ResponsePayloadListInnerColors
+ */
+ normal: string;
+ /**
+ * Hover color for the provider
+ * @type {string}
+ * @memberof ProviderList200ResponsePayloadListInnerColors
+ */
+ hover: string;
+}
+
+/**
+ * Check if a given object implements the ProviderList200ResponsePayloadListInnerColors interface.
+ */
+export function instanceOfProviderList200ResponsePayloadListInnerColors(value: object): value is ProviderList200ResponsePayloadListInnerColors {
+ if (!('normal' in value) || value['normal'] === undefined) return false;
+ if (!('hover' in value) || value['hover'] === undefined) return false;
+ return true;
+}
+
+export function ProviderList200ResponsePayloadListInnerColorsFromJSON(json: any): ProviderList200ResponsePayloadListInnerColors {
+ return ProviderList200ResponsePayloadListInnerColorsFromJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadListInnerColorsFromJSONTyped(json: any, ignoreDiscriminator: boolean): ProviderList200ResponsePayloadListInnerColors {
+ if (json == null) {
+ return json;
+ }
+ return {
+
+ 'normal': json['normal'],
+ 'hover': json['hover'],
+ };
+}
+
+export function ProviderList200ResponsePayloadListInnerColorsToJSON(json: any): ProviderList200ResponsePayloadListInnerColors {
+ return ProviderList200ResponsePayloadListInnerColorsToJSONTyped(json, false);
+}
+
+export function ProviderList200ResponsePayloadListInnerColorsToJSONTyped(value?: ProviderList200ResponsePayloadListInnerColors | null, ignoreDiscriminator: boolean = false): any {
+ if (value == null) {
+ return value;
+ }
+
+ return {
+
+ 'normal': value['normal'],
+ 'hover': value['hover'],
+ };
+}
+
diff --git a/frontend/src/api/generated/models/index.ts b/frontend/src/api/generated/models/index.ts
index 1f150d8..a4059b0 100644
--- a/frontend/src/api/generated/models/index.ts
+++ b/frontend/src/api/generated/models/index.ts
@@ -29,6 +29,10 @@ export * from './LoginOtp500Response';
export * from './LoginOtpRequest';
export * from './LoginRequest';
export * from './Logout200Response';
+export * from './ProviderList200Response';
+export * from './ProviderList200ResponsePayload';
+export * from './ProviderList200ResponsePayloadListInner';
+export * from './ProviderList200ResponsePayloadListInnerColors';
export * from './Signin200Response';
export * from './Signin200ResponsePayload';
export * from './Signin400Response';
diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts
index 5821af5..6f09284 100644
--- a/frontend/src/api/index.ts
+++ b/frontend/src/api/index.ts
@@ -3,15 +3,16 @@ export * from './generated'
const basePath = (() => {
- let u = new URL(location.href);
- u.pathname = "";
- u.hash = "";
- u.search = "";
- return u.toString().replace(/\/+$/, '');
+ let u = new URL(location.href);
+ u.pathname = "";
+ u.hash = "";
+ u.search = "";
+ return u.toString().replace(/\/+$/, '');
})();
export const client = new OpenapiOtherApi(new Configuration({ basePath }));
export default client;
+Object.assign(window as any, { apiClient: client });
diff --git a/frontend/src/pages/login/login.html b/frontend/src/pages/login/login.html
index 34f60aa..1e553ec 100644
--- a/frontend/src/pages/login/login.html
+++ b/frontend/src/pages/login/login.html
@@ -35,7 +35,7 @@
You can also login with
-
diff --git a/frontend/src/pages/login/login.ts b/frontend/src/pages/login/login.ts
index 7a4f193..901958a 100644
--- a/frontend/src/pages/login/login.ts
+++ b/frontend/src/pages/login/login.ts
@@ -83,15 +83,17 @@ function handleLogin(_url: string, _args: RouteHandlerParams): RouteHandlerRetur
let styleSheetElement = document.createElement('style');
styleSheetElement.innerText = "";
// TODO: fetch all the providers from an API ?
- const providers: Providers[] = [
+ const providersReq = await client.providerList();
+ const providers = providersReq.payload.list;
+ /*const providers: Providers[] = [
{ name: 'discord', display_name: 'Discord', color: { default: 'bg-[#5865F2]', hover: '#FF65F2' } },
{ name: 'kanidm', display_name: 'Kanidm', color: { default: 'bg-red-500', hover: 'bg-red-700' } },
{ name: 'google', display_name: 'Google' },
- ]
+ ]*/
let first = true;
for (const p of providers) {
let b = document.createElement('button');
- if (first) b.classList.add('last:col-span-2');
+ if (first && providers.length % 2) b.classList.add('last:col-span-2');
first = false;
b.classList.add(...(
'w-full text-white font-medium py-2 rounded-xl transition'
@@ -99,10 +101,10 @@ function handleLogin(_url: string, _args: RouteHandlerParams): RouteHandlerRetur
));
b.classList.add(`providerButton-${p.name}`)
- const col = { default: p.color?.default ?? "bg-gray-600", hover: p.color?.hover ?? "bg-gray-700" };
+ const col = p.colors;
for (const k of Object.keys(col)) {
- let c = (col as { [k: string]: string })[k].trim();
+ let c = (col as any)[k].trim();
if (c.startsWith('bg-')) {
c = c.replace(/^bg-/, '');
const customProp = c.match(/^\((.+)\)$/);
@@ -122,19 +124,17 @@ function handleLogin(_url: string, _args: RouteHandlerParams): RouteHandlerRetur
c = `var(--color-${c})`
}
- (col as { [k: string]: string })[k] = c;
+ (col as any)[k] = c;
}
- styleSheetElement.innerText += `.providerButton-${p.name} { background-color: ${col.default}; }\n`;
+ styleSheetElement.innerText += `.providerButton-${p.name} { background-color: ${col.normal}; }\n`;
styleSheetElement.innerText += `.providerButton-${p.name}:hover { background-color: ${col.hover}; }\n`;
- b.dataset.display_name = p.display_name;
+ b.dataset.display_name = p.displayName;
b.dataset.name = p.name;
- if (p.icon_url) b.dataset.icon = p.icon_url;
+ //if (p.icon_url) b.dataset.icon = p.icon_url;
- b.innerHTML = `
- ${p.icon_url ? `
` : ''} ${p.display_name}
- `
+ b.innerHTML = `${p.displayName}`
b.addEventListener('click', () => {
location.href = `/api/auth/oauth2/${p.name}/login`;
})
diff --git a/src/auth/extra/providers.schema.json b/src/auth/extra/providers.schema.json
index 9948356..68db068 100644
--- a/src/auth/extra/providers.schema.json
+++ b/src/auth/extra/providers.schema.json
@@ -1,186 +1 @@
-{
- "type": "object",
- "properties": {
- "providers": {
- "type": "object",
- "patternProperties": {
- "^(.*)$": {
- "anyOf": [
- {
- "type": "object",
- "properties": {
- "token_url": {
- "type": "string"
- },
- "auth_url": {
- "type": "string"
- },
- "info_url": {
- "type": "string"
- },
- "client_id": {
- "type": "string"
- },
- "client_secret": {
- "anyOf": [
- {
- "type": "object",
- "properties": {
- "env": {
- "description": "Secret is stored in the env var",
- "type": "string"
- }
- },
- "required": [
- "env"
- ]
- },
- {
- "type": "object",
- "properties": {
- "inline": {
- "description": "Secret is inline here",
- "type": "string"
- }
- },
- "required": [
- "inline"
- ]
- }
- ]
- },
- "scopes": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "redirect_url": {
- "type": "string"
- },
- "user": {
- "default": {
- "unique_id": "email",
- "name": "name"
- },
- "type": "object",
- "properties": {
- "unique_id": {
- "description": "A unique identifier for this provider",
- "default": "email",
- "type": "string"
- },
- "name": {
- "description": "A name for this provider",
- "default": "name",
- "type": "string"
- }
- },
- "required": [
- "unique_id",
- "name"
- ]
- }
- },
- "required": [
- "token_url",
- "auth_url",
- "info_url",
- "client_id",
- "client_secret",
- "scopes",
- "redirect_url",
- "user"
- ]
- },
- {
- "type": "object",
- "properties": {
- "openid_url": {
- "type": "string"
- },
- "client_id": {
- "type": "string"
- },
- "client_secret": {
- "anyOf": [
- {
- "type": "object",
- "properties": {
- "env": {
- "description": "Secret is stored in the env var",
- "type": "string"
- }
- },
- "required": [
- "env"
- ]
- },
- {
- "type": "object",
- "properties": {
- "inline": {
- "description": "Secret is inline here",
- "type": "string"
- }
- },
- "required": [
- "inline"
- ]
- }
- ]
- },
- "scopes": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "redirect_url": {
- "type": "string"
- },
- "user": {
- "default": {
- "unique_id": "email",
- "name": "name"
- },
- "type": "object",
- "properties": {
- "unique_id": {
- "description": "A unique identifier for this provider",
- "default": "email",
- "type": "string"
- },
- "name": {
- "description": "A name for this provider",
- "default": "name",
- "type": "string"
- }
- },
- "required": [
- "unique_id",
- "name"
- ]
- }
- },
- "required": [
- "openid_url",
- "client_id",
- "client_secret",
- "scopes",
- "redirect_url",
- "user"
- ]
- }
- ]
- }
- }
- },
- "$schema": {
- "type": "string"
- }
- },
- "required": [
- "providers"
- ]
-}
+{"type":"object","required":["providers"],"properties":{"providers":{"type":"object","patternProperties":{"^.*$":{"anyOf":[{"type":"object","required":["token_url","auth_url","info_url","client_id","client_secret","scopes","redirect_url","user","display_name"],"properties":{"token_url":{"type":"string"},"auth_url":{"type":"string"},"info_url":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"anyOf":[{"type":"object","required":["env"],"properties":{"env":{"type":"string","description":"Secret is stored in the env var"}}},{"type":"object","required":["inline"],"properties":{"inline":{"type":"string","description":"Secret is inline here"}}}]},"scopes":{"type":"array","items":{"type":"string"}},"redirect_url":{"type":"string"},"user":{"type":"object","required":["unique_id","name"],"properties":{"unique_id":{"type":"string","description":"A unique identifier for this provider","default":"email"},"name":{"type":"string","description":"A name for this provider","default":"name"}},"default":{"unique_id":"email","name":"name"}},"display_name":{"type":"string"},"color":{"type":"object","properties":{"default":{"type":"string"},"hover":{"type":"string"}}}}},{"type":"object","required":["openid_url","client_id","client_secret","scopes","redirect_url","user","display_name"],"properties":{"openid_url":{"type":"string"},"client_id":{"type":"string"},"client_secret":{"anyOf":[{"type":"object","required":["env"],"properties":{"env":{"type":"string","description":"Secret is stored in the env var"}}},{"type":"object","required":["inline"],"properties":{"inline":{"type":"string","description":"Secret is inline here"}}}]},"scopes":{"type":"array","items":{"type":"string"}},"redirect_url":{"type":"string"},"user":{"type":"object","required":["unique_id","name"],"properties":{"unique_id":{"type":"string","description":"A unique identifier for this provider","default":"email"},"name":{"type":"string","description":"A name for this provider","default":"name"}},"default":{"unique_id":"email","name":"name"}},"display_name":{"type":"string"},"color":{"type":"object","properties":{"default":{"type":"string"},"hover":{"type":"string"}}}}}]}}},"$schema":{"type":"string"}}}
diff --git a/src/auth/extra/providers.toml.template b/src/auth/extra/providers.toml.template
index 98f3ad5..2937f5b 100644
--- a/src/auth/extra/providers.toml.template
+++ b/src/auth/extra/providers.toml.template
@@ -9,6 +9,7 @@ scopes = ["any needed scope here", "openid", "email"]
redirect_url = "https://local.maix.me:8888/api/auth/oauth2/provider-openid/callback"
# from the `info_url` request, which json key we will take an unique provider id (default:email) and an name for the user (default:name)
user = { unique_id = "email", name = "name" }
+display_name = "OpenID 1"
[providers.discord]
auth_url = "https://discord.com/oauth2/authorize"
@@ -19,3 +20,4 @@ client_id = "CLIENT_ID"
redirect_url = "https://local.maix.me:8888/api/auth/oauth2/discord/callback"
scopes = ["identify"] # here no email asked :)
user = { unique_id = "id", name = "username" } # for example discord provides some stuff, like unique_id and username, such that we dont have to ask additional permission to get the email
+display_name = "Discord"
diff --git a/src/auth/openapi.json b/src/auth/openapi.json
index c5d3df8..6522bdf 100644
--- a/src/auth/openapi.json
+++ b/src/auth/openapi.json
@@ -195,6 +195,86 @@
}
}
},
+ "/api/auth/providerList": {
+ "get": {
+ "operationId": "providerList",
+ "responses": {
+ "200": {
+ "description": "Default Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "kind",
+ "msg",
+ "payload"
+ ],
+ "properties": {
+ "kind": {
+ "enum": [
+ "success"
+ ]
+ },
+ "msg": {
+ "enum": [
+ "providerList.success"
+ ]
+ },
+ "payload": {
+ "type": "object",
+ "required": [
+ "list"
+ ],
+ "properties": {
+ "list": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "display_name",
+ "name",
+ "colors"
+ ],
+ "properties": {
+ "display_name": {
+ "type": "string",
+ "description": "Name to display to the user"
+ },
+ "name": {
+ "type": "string",
+ "description": "internal Name of the provider"
+ },
+ "colors": {
+ "type": "object",
+ "required": [
+ "normal",
+ "hover"
+ ],
+ "properties": {
+ "normal": {
+ "type": "string",
+ "description": "Default color for the provider"
+ },
+ "hover": {
+ "type": "string",
+ "description": "Hover color for the provider"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
"/api/auth/guest": {
"post": {
"operationId": "guestLogin",
diff --git a/src/auth/src/plugins/providers.ts b/src/auth/src/plugins/providers.ts
index a5a4d3a..cf58c13 100644
--- a/src/auth/src/plugins/providers.ts
+++ b/src/auth/src/plugins/providers.ts
@@ -1,19 +1,11 @@
import { isNullish } from '@shared/utils';
import fp from 'fastify-plugin';
-import { readFile } from 'node:fs/promises';
+import { access, constants as fsConstants, readFile } from 'node:fs/promises';
import * as T from 'typebox';
import * as V from 'typebox/value';
import { Oauth2 } from '../oauth2';
import { parseTOML } from 'confbox';
-/*
-function isNullish(_v: T): boolean { return true; }
-class Oauth2 {
- constructor(..._args: any[]) { }
- static fromProvider(..._args: any[]): Oauth2 { throw 'yes'; }
-}
-*/
-
const ProviderSecret = T.Union([
T.Object({
env: T.String({ description: 'Secret is stored in the env var' }),
@@ -21,10 +13,19 @@ const ProviderSecret = T.Union([
T.Object({ inline: T.String({ description: 'Secret is inline here' }) }),
]);
-const ProviderUserInfo = T.Object({
- unique_id: T.String({ description: 'A unique identifier for this provider', default: 'email' }),
- name: T.String({ description: 'A name for this provider', default: 'name' }),
-}, { default: { unique_id: 'email', name: 'name' } });
+const ProviderUserInfo = T.Object(
+ {
+ unique_id: T.String({
+ description: 'A unique identifier for this provider',
+ default: 'email',
+ }),
+ name: T.String({
+ description: 'A name for this provider',
+ default: 'name',
+ }),
+ },
+ { default: { unique_id: 'email', name: 'name' } },
+);
const RawProviderBase = {
client_id: T.String(),
@@ -32,6 +33,13 @@ const RawProviderBase = {
scopes: T.Array(T.String()),
redirect_url: T.String(),
user: ProviderUserInfo,
+ display_name: T.String(),
+ color: T.Optional(
+ T.Object({
+ default: T.Optional(T.String()),
+ hover: T.Optional(T.String()),
+ }),
+ ),
};
const ProviderBase = T.Object(RawProviderBase);
@@ -49,6 +57,8 @@ const ProviderMapFile = T.Object({
$schema: T.Optional(T.String()),
});
+// console.log(JSON.stringify(ProviderMapFile))
+
export type ProviderSecret = T.Static;
export type ProviderUserInfo = T.Static;
export type ProviderBase = T.Static;
@@ -58,10 +68,15 @@ export type Provider = T.Static;
export type ProviderMap = T.Static;
export type ProviderMapFile = T.Static;
-
async function buildProviderMap(): Promise {
const providerFile = process.env.PROVIDER_FILE;
- if (isNullish(providerFile)) throw 'PROVIDER_FILE env var not provided';
+ if (isNullish(providerFile)) return {};
+ try {
+ await access(providerFile, fsConstants.F_OK | fsConstants.R_OK);
+ }
+ catch {
+ return {};
+ }
const data = await readFile(providerFile, { encoding: 'utf-8' });
const dataJson = parseTOML(data);
return V.Parse(ProviderMapFile, dataJson).providers;
@@ -73,7 +88,9 @@ declare module 'fastify' {
oauth2: { [k: string]: Oauth2 };
}
}
-async function makeAllOauth2(providers: ProviderMap): Promise<{ [k: string]: Oauth2 }> {
+async function makeAllOauth2(
+ providers: ProviderMap,
+): Promise<{ [k: string]: Oauth2 }> {
const out: { [k: string]: Oauth2 } = {};
for (const [k, v] of Object.entries(providers)) {
out[k] = await Oauth2.fromProvider(k, v);
diff --git a/src/auth/src/routes/getProviderList.ts b/src/auth/src/routes/getProviderList.ts
new file mode 100644
index 0000000..ea1d216
--- /dev/null
+++ b/src/auth/src/routes/getProviderList.ts
@@ -0,0 +1,46 @@
+import { FastifyPluginAsync } from 'fastify';
+
+import { Type } from 'typebox';
+import { typeResponse, MakeStaticResponse } from '@shared/utils';
+
+export const ProviderListRes = {
+ '200': typeResponse('success', 'providerList.success', {
+ list: Type.Array(Type.Object({
+ display_name: Type.String({ description: 'Name to display to the user' }),
+ name: Type.String({ description: 'internal Name of the provider' }),
+ colors: Type.Object({
+ normal: Type.String({ description: 'Default color for the provider' }),
+ hover: Type.String({ description: 'Hover color for the provider' }),
+ }),
+ })),
+ }),
+};
+
+export type ProviderListRes = MakeStaticResponse;
+
+const route: FastifyPluginAsync = async (fastify, _opts): Promise => {
+ void _opts;
+ fastify.get<{ Reply: ProviderListRes }>(
+ '/api/auth/providerList',
+ { schema: { response: ProviderListRes, operationId: 'providerList' } },
+ async function(req, res) {
+ void req;
+
+ const list = Object.entries(this.providers).map(([providerName, provider]) => {
+ const colors = provider.color ?? {};
+ return {
+ display_name: provider.display_name,
+ name: providerName,
+ colors: {
+ normal: colors.default ?? 'bg-blue-600',
+ hover: colors.hover ?? 'bg-blue-700',
+ },
+ };
+ });
+
+ return res.makeResponse(200, 'success', 'providerList.success', { list });
+ },
+ );
+};
+
+export default route;
diff --git a/src/auth/src/routes/oauth2/login.ts b/src/auth/src/routes/oauth2/login.ts
index 3a4fa01..2f17e44 100644
--- a/src/auth/src/routes/oauth2/login.ts
+++ b/src/auth/src/routes/oauth2/login.ts
@@ -18,7 +18,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise => {
const [url, _csrf, _nonce] = u.intoUrl();
void _csrf; void _nonce;
- return res.setCookie('pkce', verifier.secret).redirect(url.toString());
+ return res.setCookie('pkce', verifier.secret, { path:'/' }).redirect(url.toString());
},
);
};
diff --git a/src/openapi.json b/src/openapi.json
index f466254..4327973 100644
--- a/src/openapi.json
+++ b/src/openapi.json
@@ -214,6 +214,89 @@
]
}
},
+ "/api/auth/providerList": {
+ "get": {
+ "operationId": "providerList",
+ "responses": {
+ "200": {
+ "description": "Default Response",
+ "content": {
+ "application/json": {
+ "schema": {
+ "type": "object",
+ "required": [
+ "kind",
+ "msg",
+ "payload"
+ ],
+ "properties": {
+ "kind": {
+ "enum": [
+ "success"
+ ]
+ },
+ "msg": {
+ "enum": [
+ "providerList.success"
+ ]
+ },
+ "payload": {
+ "type": "object",
+ "required": [
+ "list"
+ ],
+ "properties": {
+ "list": {
+ "type": "array",
+ "items": {
+ "type": "object",
+ "required": [
+ "display_name",
+ "name",
+ "colors"
+ ],
+ "properties": {
+ "display_name": {
+ "type": "string",
+ "description": "Name to display to the user"
+ },
+ "name": {
+ "type": "string",
+ "description": "internal Name of the provider"
+ },
+ "colors": {
+ "type": "object",
+ "required": [
+ "normal",
+ "hover"
+ ],
+ "properties": {
+ "normal": {
+ "type": "string",
+ "description": "Default color for the provider"
+ },
+ "hover": {
+ "type": "string",
+ "description": "Hover color for the provider"
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "tags": [
+ "openapi_other"
+ ]
+ }
+ },
"/api/auth/guest": {
"post": {
"operationId": "guestLogin",