This commit is contained in:
Maieul BOYER 2025-12-01 19:26:22 +01:00
parent e5b71a5cc1
commit c898fe8d32
No known key found for this signature in database
30 changed files with 1148 additions and 95 deletions

View file

@ -2,15 +2,19 @@ apis/OpenapiOtherApi.ts
apis/index.ts apis/index.ts
index.ts index.ts
models/ChatTest200Response.ts models/ChatTest200Response.ts
models/ChatTest200ResponsePayload.ts
models/DisableOtp200Response.ts models/DisableOtp200Response.ts
models/DisableOtp400Response.ts
models/DisableOtp401Response.ts models/DisableOtp401Response.ts
models/DisableOtp500Response.ts models/DisableOtp500Response.ts
models/EnableOtp200Response.ts models/EnableOtp200Response.ts
models/EnableOtp200ResponsePayload.ts models/EnableOtp200ResponsePayload.ts
models/EnableOtp400Response.ts
models/EnableOtp401Response.ts models/EnableOtp401Response.ts
models/EnableOtp401ResponseAnyOf.ts models/EnableOtp401ResponseAnyOf.ts
models/GetUser200Response.ts models/GetUser200Response.ts
models/GetUser200ResponsePayload.ts models/GetUser200ResponsePayload.ts
models/GetUser200ResponsePayloadSelfInfo.ts
models/GetUser403Response.ts models/GetUser403Response.ts
models/GetUser404Response.ts models/GetUser404Response.ts
models/GetUserUserParameter.ts models/GetUserUserParameter.ts
@ -41,6 +45,7 @@ models/Signin500Response.ts
models/StatusOtp200Response.ts models/StatusOtp200Response.ts
models/StatusOtp200ResponseAnyOf.ts models/StatusOtp200ResponseAnyOf.ts
models/StatusOtp200ResponseAnyOf1.ts models/StatusOtp200ResponseAnyOf1.ts
models/StatusOtp200ResponseAnyOfPayload.ts
models/StatusOtp401Response.ts models/StatusOtp401Response.ts
models/StatusOtp500Response.ts models/StatusOtp500Response.ts
models/index.ts models/index.ts

View file

@ -17,9 +17,11 @@ import * as runtime from '../runtime';
import type { import type {
ChatTest200Response, ChatTest200Response,
DisableOtp200Response, DisableOtp200Response,
DisableOtp400Response,
DisableOtp401Response, DisableOtp401Response,
DisableOtp500Response, DisableOtp500Response,
EnableOtp200Response, EnableOtp200Response,
EnableOtp400Response,
EnableOtp401Response, EnableOtp401Response,
GetUser200Response, GetUser200Response,
GetUser403Response, GetUser403Response,
@ -51,12 +53,16 @@ import {
ChatTest200ResponseToJSON, ChatTest200ResponseToJSON,
DisableOtp200ResponseFromJSON, DisableOtp200ResponseFromJSON,
DisableOtp200ResponseToJSON, DisableOtp200ResponseToJSON,
DisableOtp400ResponseFromJSON,
DisableOtp400ResponseToJSON,
DisableOtp401ResponseFromJSON, DisableOtp401ResponseFromJSON,
DisableOtp401ResponseToJSON, DisableOtp401ResponseToJSON,
DisableOtp500ResponseFromJSON, DisableOtp500ResponseFromJSON,
DisableOtp500ResponseToJSON, DisableOtp500ResponseToJSON,
EnableOtp200ResponseFromJSON, EnableOtp200ResponseFromJSON,
EnableOtp200ResponseToJSON, EnableOtp200ResponseToJSON,
EnableOtp400ResponseFromJSON,
EnableOtp400ResponseToJSON,
EnableOtp401ResponseFromJSON, EnableOtp401ResponseFromJSON,
EnableOtp401ResponseToJSON, EnableOtp401ResponseToJSON,
GetUser200ResponseFromJSON, GetUser200ResponseFromJSON,
@ -174,7 +180,7 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
/** /**
*/ */
async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<DisableOtp200Response | DisableOtp401Response | DisableOtp500Response>> { async disableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<DisableOtp200Response | DisableOtp400Response | DisableOtp401Response | DisableOtp500Response>> {
const queryParameters: any = {}; const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
@ -197,6 +203,10 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
// Object response for status 200 // Object response for status 200
return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp200ResponseFromJSON(jsonValue)); return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp200ResponseFromJSON(jsonValue));
} }
if (response.status === 400) {
// Object response for status 400
return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp400ResponseFromJSON(jsonValue));
}
if (response.status === 401) { if (response.status === 401) {
// Object response for status 401 // Object response for status 401
return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue)); return new runtime.JSONApiResponse(response, (jsonValue) => DisableOtp401ResponseFromJSON(jsonValue));
@ -208,19 +218,19 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
// CHANGED: Throw error if status code is not handled by any of the defined responses // 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 // 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 // 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, 401, 500`); throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401, 500`);
} }
/** /**
*/ */
async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<DisableOtp200Response | DisableOtp401Response | DisableOtp500Response> { async disableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<DisableOtp200Response | DisableOtp400Response | DisableOtp401Response | DisableOtp500Response> {
const response = await this.disableOtpRaw(initOverrides); const response = await this.disableOtpRaw(initOverrides);
return await response.value(); return await response.value();
} }
/** /**
*/ */
async enableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnableOtp200Response | EnableOtp401Response>> { async enableOtpRaw(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<runtime.ApiResponse<EnableOtp200Response | EnableOtp400Response | EnableOtp401Response>> {
const queryParameters: any = {}; const queryParameters: any = {};
const headerParameters: runtime.HTTPHeaders = {}; const headerParameters: runtime.HTTPHeaders = {};
@ -243,6 +253,10 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
// Object response for status 200 // Object response for status 200
return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp200ResponseFromJSON(jsonValue)); return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp200ResponseFromJSON(jsonValue));
} }
if (response.status === 400) {
// Object response for status 400
return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp400ResponseFromJSON(jsonValue));
}
if (response.status === 401) { if (response.status === 401) {
// Object response for status 401 // Object response for status 401
return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp401ResponseFromJSON(jsonValue)); return new runtime.JSONApiResponse(response, (jsonValue) => EnableOtp401ResponseFromJSON(jsonValue));
@ -250,12 +264,12 @@ export class OpenapiOtherApi extends runtime.BaseAPI {
// CHANGED: Throw error if status code is not handled by any of the defined responses // 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 // 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 // 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, 401`); throw new runtime.ResponseError(response, `Unexpected status code: ${response.status}. Expected one of: 200, 400, 401`);
} }
/** /**
*/ */
async enableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnableOtp200Response | EnableOtp401Response> { async enableOtp(initOverrides?: RequestInit | runtime.InitOverrideFunction): Promise<EnableOtp200Response | EnableOtp400Response | EnableOtp401Response> {
const response = await this.enableOtpRaw(initOverrides); const response = await this.enableOtpRaw(initOverrides);
return await response.value(); return await response.value();
} }

View file

@ -13,13 +13,13 @@
*/ */
import { mapValues } from '../runtime'; import { mapValues } from '../runtime';
import type { GetUser200ResponsePayload } from './GetUser200ResponsePayload'; import type { ChatTest200ResponsePayload } from './ChatTest200ResponsePayload';
import { import {
GetUser200ResponsePayloadFromJSON, ChatTest200ResponsePayloadFromJSON,
GetUser200ResponsePayloadFromJSONTyped, ChatTest200ResponsePayloadFromJSONTyped,
GetUser200ResponsePayloadToJSON, ChatTest200ResponsePayloadToJSON,
GetUser200ResponsePayloadToJSONTyped, ChatTest200ResponsePayloadToJSONTyped,
} from './GetUser200ResponsePayload'; } from './ChatTest200ResponsePayload';
/** /**
* *
@ -41,10 +41,10 @@ export interface ChatTest200Response {
msg: ChatTest200ResponseMsgEnum; msg: ChatTest200ResponseMsgEnum;
/** /**
* *
* @type {GetUser200ResponsePayload} * @type {ChatTest200ResponsePayload}
* @memberof ChatTest200Response * @memberof ChatTest200Response
*/ */
payload: GetUser200ResponsePayload; payload: ChatTest200ResponsePayload;
} }
@ -87,7 +87,7 @@ export function ChatTest200ResponseFromJSONTyped(json: any, ignoreDiscriminator:
'kind': json['kind'], 'kind': json['kind'],
'msg': json['msg'], 'msg': json['msg'],
'payload': GetUser200ResponsePayloadFromJSON(json['payload']), 'payload': ChatTest200ResponsePayloadFromJSON(json['payload']),
}; };
} }
@ -104,7 +104,7 @@ export function ChatTest200ResponseToJSONTyped(value?: ChatTest200Response | nul
'kind': value['kind'], 'kind': value['kind'],
'msg': value['msg'], 'msg': value['msg'],
'payload': GetUser200ResponsePayloadToJSON(value['payload']), 'payload': ChatTest200ResponsePayloadToJSON(value['payload']),
}; };
} }

View file

@ -0,0 +1,84 @@
/* 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 ChatTest200ResponsePayload
*/
export interface ChatTest200ResponsePayload {
/**
*
* @type {string}
* @memberof ChatTest200ResponsePayload
*/
name: string;
/**
*
* @type {string}
* @memberof ChatTest200ResponsePayload
*/
id: string;
/**
*
* @type {boolean}
* @memberof ChatTest200ResponsePayload
*/
guest: boolean;
}
/**
* Check if a given object implements the ChatTest200ResponsePayload interface.
*/
export function instanceOfChatTest200ResponsePayload(value: object): value is ChatTest200ResponsePayload {
if (!('name' in value) || value['name'] === undefined) return false;
if (!('id' in value) || value['id'] === undefined) return false;
if (!('guest' in value) || value['guest'] === undefined) return false;
return true;
}
export function ChatTest200ResponsePayloadFromJSON(json: any): ChatTest200ResponsePayload {
return ChatTest200ResponsePayloadFromJSONTyped(json, false);
}
export function ChatTest200ResponsePayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): ChatTest200ResponsePayload {
if (json == null) {
return json;
}
return {
'name': json['name'],
'id': json['id'],
'guest': json['guest'],
};
}
export function ChatTest200ResponsePayloadToJSON(json: any): ChatTest200ResponsePayload {
return ChatTest200ResponsePayloadToJSONTyped(json, false);
}
export function ChatTest200ResponsePayloadToJSONTyped(value?: ChatTest200ResponsePayload | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'name': value['name'],
'id': value['id'],
'guest': value['guest'],
};
}

View file

@ -0,0 +1,93 @@
/* 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 DisableOtp400Response
*/
export interface DisableOtp400Response {
/**
*
* @type {string}
* @memberof DisableOtp400Response
*/
kind: DisableOtp400ResponseKindEnum;
/**
*
* @type {string}
* @memberof DisableOtp400Response
*/
msg: DisableOtp400ResponseMsgEnum;
}
/**
* @export
*/
export const DisableOtp400ResponseKindEnum = {
Failure: 'failure'
} as const;
export type DisableOtp400ResponseKindEnum = typeof DisableOtp400ResponseKindEnum[keyof typeof DisableOtp400ResponseKindEnum];
/**
* @export
*/
export const DisableOtp400ResponseMsgEnum = {
DisableOtpFailureGuest: 'disableOtp.failure.guest'
} as const;
export type DisableOtp400ResponseMsgEnum = typeof DisableOtp400ResponseMsgEnum[keyof typeof DisableOtp400ResponseMsgEnum];
/**
* Check if a given object implements the DisableOtp400Response interface.
*/
export function instanceOfDisableOtp400Response(value: object): value is DisableOtp400Response {
if (!('kind' in value) || value['kind'] === undefined) return false;
if (!('msg' in value) || value['msg'] === undefined) return false;
return true;
}
export function DisableOtp400ResponseFromJSON(json: any): DisableOtp400Response {
return DisableOtp400ResponseFromJSONTyped(json, false);
}
export function DisableOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): DisableOtp400Response {
if (json == null) {
return json;
}
return {
'kind': json['kind'],
'msg': json['msg'],
};
}
export function DisableOtp400ResponseToJSON(json: any): DisableOtp400Response {
return DisableOtp400ResponseToJSONTyped(json, false);
}
export function DisableOtp400ResponseToJSONTyped(value?: DisableOtp400Response | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'kind': value['kind'],
'msg': value['msg'],
};
}

View file

@ -0,0 +1,93 @@
/* 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 EnableOtp400Response
*/
export interface EnableOtp400Response {
/**
*
* @type {string}
* @memberof EnableOtp400Response
*/
kind: EnableOtp400ResponseKindEnum;
/**
*
* @type {string}
* @memberof EnableOtp400Response
*/
msg: EnableOtp400ResponseMsgEnum;
}
/**
* @export
*/
export const EnableOtp400ResponseKindEnum = {
Failure: 'failure'
} as const;
export type EnableOtp400ResponseKindEnum = typeof EnableOtp400ResponseKindEnum[keyof typeof EnableOtp400ResponseKindEnum];
/**
* @export
*/
export const EnableOtp400ResponseMsgEnum = {
EnableOtpFailureGuest: 'enableOtp.failure.guest'
} as const;
export type EnableOtp400ResponseMsgEnum = typeof EnableOtp400ResponseMsgEnum[keyof typeof EnableOtp400ResponseMsgEnum];
/**
* Check if a given object implements the EnableOtp400Response interface.
*/
export function instanceOfEnableOtp400Response(value: object): value is EnableOtp400Response {
if (!('kind' in value) || value['kind'] === undefined) return false;
if (!('msg' in value) || value['msg'] === undefined) return false;
return true;
}
export function EnableOtp400ResponseFromJSON(json: any): EnableOtp400Response {
return EnableOtp400ResponseFromJSONTyped(json, false);
}
export function EnableOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): EnableOtp400Response {
if (json == null) {
return json;
}
return {
'kind': json['kind'],
'msg': json['msg'],
};
}
export function EnableOtp400ResponseToJSON(json: any): EnableOtp400Response {
return EnableOtp400ResponseToJSONTyped(json, false);
}
export function EnableOtp400ResponseToJSONTyped(value?: EnableOtp400Response | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'kind': value['kind'],
'msg': value['msg'],
};
}

View file

@ -13,6 +13,14 @@
*/ */
import { mapValues } from '../runtime'; import { mapValues } from '../runtime';
import type { GetUser200ResponsePayloadSelfInfo } from './GetUser200ResponsePayloadSelfInfo';
import {
GetUser200ResponsePayloadSelfInfoFromJSON,
GetUser200ResponsePayloadSelfInfoFromJSONTyped,
GetUser200ResponsePayloadSelfInfoToJSON,
GetUser200ResponsePayloadSelfInfoToJSONTyped,
} from './GetUser200ResponsePayloadSelfInfo';
/** /**
* *
* @export * @export
@ -37,6 +45,12 @@ export interface GetUser200ResponsePayload {
* @memberof GetUser200ResponsePayload * @memberof GetUser200ResponsePayload
*/ */
guest: boolean; guest: boolean;
/**
*
* @type {GetUser200ResponsePayloadSelfInfo}
* @memberof GetUser200ResponsePayload
*/
selfInfo?: GetUser200ResponsePayloadSelfInfo;
} }
/** /**
@ -62,6 +76,7 @@ export function GetUser200ResponsePayloadFromJSONTyped(json: any, ignoreDiscrimi
'name': json['name'], 'name': json['name'],
'id': json['id'], 'id': json['id'],
'guest': json['guest'], 'guest': json['guest'],
'selfInfo': json['selfInfo'] == null ? undefined : GetUser200ResponsePayloadSelfInfoFromJSON(json['selfInfo']),
}; };
} }
@ -79,6 +94,7 @@ export function GetUser200ResponsePayloadToJSONTyped(value?: GetUser200ResponseP
'name': value['name'], 'name': value['name'],
'id': value['id'], 'id': value['id'],
'guest': value['guest'], 'guest': value['guest'],
'selfInfo': GetUser200ResponsePayloadSelfInfoToJSON(value['selfInfo']),
}; };
} }

View file

@ -0,0 +1,81 @@
/* 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 GetUser200ResponsePayloadSelfInfo
*/
export interface GetUser200ResponsePayloadSelfInfo {
/**
*
* @type {string}
* @memberof GetUser200ResponsePayloadSelfInfo
*/
loginName?: string;
/**
*
* @type {string}
* @memberof GetUser200ResponsePayloadSelfInfo
*/
providerId?: string;
/**
*
* @type {string}
* @memberof GetUser200ResponsePayloadSelfInfo
*/
providerUser?: string;
}
/**
* Check if a given object implements the GetUser200ResponsePayloadSelfInfo interface.
*/
export function instanceOfGetUser200ResponsePayloadSelfInfo(value: object): value is GetUser200ResponsePayloadSelfInfo {
return true;
}
export function GetUser200ResponsePayloadSelfInfoFromJSON(json: any): GetUser200ResponsePayloadSelfInfo {
return GetUser200ResponsePayloadSelfInfoFromJSONTyped(json, false);
}
export function GetUser200ResponsePayloadSelfInfoFromJSONTyped(json: any, ignoreDiscriminator: boolean): GetUser200ResponsePayloadSelfInfo {
if (json == null) {
return json;
}
return {
'loginName': json['login_name'] == null ? undefined : json['login_name'],
'providerId': json['provider_id'] == null ? undefined : json['provider_id'],
'providerUser': json['provider_user'] == null ? undefined : json['provider_user'],
};
}
export function GetUser200ResponsePayloadSelfInfoToJSON(json: any): GetUser200ResponsePayloadSelfInfo {
return GetUser200ResponsePayloadSelfInfoToJSONTyped(json, false);
}
export function GetUser200ResponsePayloadSelfInfoToJSONTyped(value?: GetUser200ResponsePayloadSelfInfo | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'login_name': value['loginName'],
'provider_id': value['providerId'],
'provider_user': value['providerUser'],
};
}

View file

@ -13,13 +13,6 @@
*/ */
import { mapValues } from '../runtime'; import { mapValues } from '../runtime';
import type { EnableOtp200ResponsePayload } from './EnableOtp200ResponsePayload';
import {
EnableOtp200ResponsePayloadFromJSON,
EnableOtp200ResponsePayloadFromJSONTyped,
EnableOtp200ResponsePayloadToJSON,
EnableOtp200ResponsePayloadToJSONTyped,
} from './EnableOtp200ResponsePayload';
import type { StatusOtp200ResponseAnyOf } from './StatusOtp200ResponseAnyOf'; import type { StatusOtp200ResponseAnyOf } from './StatusOtp200ResponseAnyOf';
import { import {
StatusOtp200ResponseAnyOfFromJSON, StatusOtp200ResponseAnyOfFromJSON,
@ -27,6 +20,13 @@ import {
StatusOtp200ResponseAnyOfToJSON, StatusOtp200ResponseAnyOfToJSON,
StatusOtp200ResponseAnyOfToJSONTyped, StatusOtp200ResponseAnyOfToJSONTyped,
} from './StatusOtp200ResponseAnyOf'; } from './StatusOtp200ResponseAnyOf';
import type { StatusOtp200ResponseAnyOfPayload } from './StatusOtp200ResponseAnyOfPayload';
import {
StatusOtp200ResponseAnyOfPayloadFromJSON,
StatusOtp200ResponseAnyOfPayloadFromJSONTyped,
StatusOtp200ResponseAnyOfPayloadToJSON,
StatusOtp200ResponseAnyOfPayloadToJSONTyped,
} from './StatusOtp200ResponseAnyOfPayload';
import type { StatusOtp200ResponseAnyOf1 } from './StatusOtp200ResponseAnyOf1'; import type { StatusOtp200ResponseAnyOf1 } from './StatusOtp200ResponseAnyOf1';
import { import {
StatusOtp200ResponseAnyOf1FromJSON, StatusOtp200ResponseAnyOf1FromJSON,
@ -55,10 +55,10 @@ export interface StatusOtp200Response {
msg: StatusOtp200ResponseMsgEnum; msg: StatusOtp200ResponseMsgEnum;
/** /**
* *
* @type {EnableOtp200ResponsePayload} * @type {StatusOtp200ResponseAnyOfPayload}
* @memberof StatusOtp200Response * @memberof StatusOtp200Response
*/ */
payload: EnableOtp200ResponsePayload; payload: StatusOtp200ResponseAnyOfPayload;
} }
@ -101,7 +101,7 @@ export function StatusOtp200ResponseFromJSONTyped(json: any, ignoreDiscriminator
'kind': json['kind'], 'kind': json['kind'],
'msg': json['msg'], 'msg': json['msg'],
'payload': EnableOtp200ResponsePayloadFromJSON(json['payload']), 'payload': StatusOtp200ResponseAnyOfPayloadFromJSON(json['payload']),
}; };
} }
@ -118,7 +118,7 @@ export function StatusOtp200ResponseToJSONTyped(value?: StatusOtp200Response | n
'kind': value['kind'], 'kind': value['kind'],
'msg': value['msg'], 'msg': value['msg'],
'payload': EnableOtp200ResponsePayloadToJSON(value['payload']), 'payload': StatusOtp200ResponseAnyOfPayloadToJSON(value['payload']),
}; };
} }

View file

@ -13,13 +13,13 @@
*/ */
import { mapValues } from '../runtime'; import { mapValues } from '../runtime';
import type { EnableOtp200ResponsePayload } from './EnableOtp200ResponsePayload'; import type { StatusOtp200ResponseAnyOfPayload } from './StatusOtp200ResponseAnyOfPayload';
import { import {
EnableOtp200ResponsePayloadFromJSON, StatusOtp200ResponseAnyOfPayloadFromJSON,
EnableOtp200ResponsePayloadFromJSONTyped, StatusOtp200ResponseAnyOfPayloadFromJSONTyped,
EnableOtp200ResponsePayloadToJSON, StatusOtp200ResponseAnyOfPayloadToJSON,
EnableOtp200ResponsePayloadToJSONTyped, StatusOtp200ResponseAnyOfPayloadToJSONTyped,
} from './EnableOtp200ResponsePayload'; } from './StatusOtp200ResponseAnyOfPayload';
/** /**
* *
@ -41,10 +41,10 @@ export interface StatusOtp200ResponseAnyOf {
msg: StatusOtp200ResponseAnyOfMsgEnum; msg: StatusOtp200ResponseAnyOfMsgEnum;
/** /**
* *
* @type {EnableOtp200ResponsePayload} * @type {StatusOtp200ResponseAnyOfPayload}
* @memberof StatusOtp200ResponseAnyOf * @memberof StatusOtp200ResponseAnyOf
*/ */
payload: EnableOtp200ResponsePayload; payload: StatusOtp200ResponseAnyOfPayload;
} }
@ -87,7 +87,7 @@ export function StatusOtp200ResponseAnyOfFromJSONTyped(json: any, ignoreDiscrimi
'kind': json['kind'], 'kind': json['kind'],
'msg': json['msg'], 'msg': json['msg'],
'payload': EnableOtp200ResponsePayloadFromJSON(json['payload']), 'payload': StatusOtp200ResponseAnyOfPayloadFromJSON(json['payload']),
}; };
} }
@ -104,7 +104,7 @@ export function StatusOtp200ResponseAnyOfToJSONTyped(value?: StatusOtp200Respons
'kind': value['kind'], 'kind': value['kind'],
'msg': value['msg'], 'msg': value['msg'],
'payload': EnableOtp200ResponsePayloadToJSON(value['payload']), 'payload': StatusOtp200ResponseAnyOfPayloadToJSON(value['payload']),
}; };
} }

View file

@ -0,0 +1,66 @@
/* 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 StatusOtp200ResponseAnyOfPayload
*/
export interface StatusOtp200ResponseAnyOfPayload {
/**
* The otp secret
* @type {string}
* @memberof StatusOtp200ResponseAnyOfPayload
*/
secret: string;
}
/**
* Check if a given object implements the StatusOtp200ResponseAnyOfPayload interface.
*/
export function instanceOfStatusOtp200ResponseAnyOfPayload(value: object): value is StatusOtp200ResponseAnyOfPayload {
if (!('secret' in value) || value['secret'] === undefined) return false;
return true;
}
export function StatusOtp200ResponseAnyOfPayloadFromJSON(json: any): StatusOtp200ResponseAnyOfPayload {
return StatusOtp200ResponseAnyOfPayloadFromJSONTyped(json, false);
}
export function StatusOtp200ResponseAnyOfPayloadFromJSONTyped(json: any, ignoreDiscriminator: boolean): StatusOtp200ResponseAnyOfPayload {
if (json == null) {
return json;
}
return {
'secret': json['secret'],
};
}
export function StatusOtp200ResponseAnyOfPayloadToJSON(json: any): StatusOtp200ResponseAnyOfPayload {
return StatusOtp200ResponseAnyOfPayloadToJSONTyped(json, false);
}
export function StatusOtp200ResponseAnyOfPayloadToJSONTyped(value?: StatusOtp200ResponseAnyOfPayload | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'secret': value['secret'],
};
}

View file

@ -0,0 +1,93 @@
/* 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 StatusOtp400Response
*/
export interface StatusOtp400Response {
/**
*
* @type {string}
* @memberof StatusOtp400Response
*/
kind: StatusOtp400ResponseKindEnum;
/**
*
* @type {string}
* @memberof StatusOtp400Response
*/
msg: StatusOtp400ResponseMsgEnum;
}
/**
* @export
*/
export const StatusOtp400ResponseKindEnum = {
Failure: 'failure'
} as const;
export type StatusOtp400ResponseKindEnum = typeof StatusOtp400ResponseKindEnum[keyof typeof StatusOtp400ResponseKindEnum];
/**
* @export
*/
export const StatusOtp400ResponseMsgEnum = {
StatusOtpFailureGuest: 'statusOtp.failure.guest'
} as const;
export type StatusOtp400ResponseMsgEnum = typeof StatusOtp400ResponseMsgEnum[keyof typeof StatusOtp400ResponseMsgEnum];
/**
* Check if a given object implements the StatusOtp400Response interface.
*/
export function instanceOfStatusOtp400Response(value: object): value is StatusOtp400Response {
if (!('kind' in value) || value['kind'] === undefined) return false;
if (!('msg' in value) || value['msg'] === undefined) return false;
return true;
}
export function StatusOtp400ResponseFromJSON(json: any): StatusOtp400Response {
return StatusOtp400ResponseFromJSONTyped(json, false);
}
export function StatusOtp400ResponseFromJSONTyped(json: any, ignoreDiscriminator: boolean): StatusOtp400Response {
if (json == null) {
return json;
}
return {
'kind': json['kind'],
'msg': json['msg'],
};
}
export function StatusOtp400ResponseToJSON(json: any): StatusOtp400Response {
return StatusOtp400ResponseToJSONTyped(json, false);
}
export function StatusOtp400ResponseToJSONTyped(value?: StatusOtp400Response | null, ignoreDiscriminator: boolean = false): any {
if (value == null) {
return value;
}
return {
'kind': value['kind'],
'msg': value['msg'],
};
}

View file

@ -1,15 +1,19 @@
/* tslint:disable */ /* tslint:disable */
/* eslint-disable */ /* eslint-disable */
export * from './ChatTest200Response'; export * from './ChatTest200Response';
export * from './ChatTest200ResponsePayload';
export * from './DisableOtp200Response'; export * from './DisableOtp200Response';
export * from './DisableOtp400Response';
export * from './DisableOtp401Response'; export * from './DisableOtp401Response';
export * from './DisableOtp500Response'; export * from './DisableOtp500Response';
export * from './EnableOtp200Response'; export * from './EnableOtp200Response';
export * from './EnableOtp200ResponsePayload'; export * from './EnableOtp200ResponsePayload';
export * from './EnableOtp400Response';
export * from './EnableOtp401Response'; export * from './EnableOtp401Response';
export * from './EnableOtp401ResponseAnyOf'; export * from './EnableOtp401ResponseAnyOf';
export * from './GetUser200Response'; export * from './GetUser200Response';
export * from './GetUser200ResponsePayload'; export * from './GetUser200ResponsePayload';
export * from './GetUser200ResponsePayloadSelfInfo';
export * from './GetUser403Response'; export * from './GetUser403Response';
export * from './GetUser404Response'; export * from './GetUser404Response';
export * from './GetUserUserParameter'; export * from './GetUserUserParameter';
@ -40,5 +44,6 @@ export * from './Signin500Response';
export * from './StatusOtp200Response'; export * from './StatusOtp200Response';
export * from './StatusOtp200ResponseAnyOf'; export * from './StatusOtp200ResponseAnyOf';
export * from './StatusOtp200ResponseAnyOf1'; export * from './StatusOtp200ResponseAnyOf1';
export * from './StatusOtp200ResponseAnyOfPayload';
export * from './StatusOtp401Response'; export * from './StatusOtp401Response';
export * from './StatusOtp500Response'; export * from './StatusOtp500Response';

View file

@ -2,49 +2,54 @@ import { showError } from "@app/toast";
import client from '@app/api'; import client from '@app/api';
export type User = { export type User = {
id: string; id: string;
guest: boolean; guest: boolean;
name: string; name: string;
selfInfo?: {
loginName?: string;
provider_id?: string;
provider_user?: string;
}
}; };
let currentUser: User | null = null; let currentUser: User | null = null;
export function getUser(): Readonly<User> | null { export function getUser(): Readonly<User> | null {
return currentUser; return currentUser;
} }
export function isLogged(): boolean { export function isLogged(): boolean {
return currentUser !== null; return currentUser !== null;
} }
export function setUser(newUser: User | null) { export function setUser(newUser: User | null) {
currentUser = newUser; currentUser = newUser;
} }
export async function updateUser(): Promise<Readonly<User> | null> { export async function updateUser(): Promise<Readonly<User> | null> {
try { try {
let res = await client.getUser({ user: 'me' }); let res = await client.getUser({ user: 'me' });
if (res.kind === "success") { if (res.kind === "success") {
setUser(res.payload); setUser(res.payload);
return res.payload; return res.payload;
} else if (res.kind === "failure") { } else if (res.kind === "failure") {
// well no user :D // well no user :D
setUser(null); setUser(null);
return null; return null;
} else if (res.kind === "notLoggedIn") { } else if (res.kind === "notLoggedIn") {
setUser(null); setUser(null);
return null; return null;
} else { } else {
setUser(null); setUser(null);
showError(`unknown response: ${JSON.stringify(res)}`); showError(`unknown response: ${JSON.stringify(res)}`);
return null; return null;
} }
} catch (e) { } catch (e) {
setUser(null); setUser(null);
showError(`failed to get user: ${e}`); showError(`failed to get user: ${e}`);
return null; return null;
} }
} }
Object.assign(window as any, { getUser, setUser, updateUser, isLogged }); Object.assign(window as any, { getUser, setUser, updateUser, isLogged });

View file

@ -3,6 +3,7 @@ import './root/root.ts'
import './chat/chat.ts' import './chat/chat.ts'
import './login/login.ts' import './login/login.ts'
import './signin/signin.ts' import './signin/signin.ts'
import './profile/profile.ts'
// ---- Initial load ---- // ---- Initial load ----
setTitle(""); setTitle("");

View file

@ -5,14 +5,98 @@ import {
type RouteHandlerParams, type RouteHandlerParams,
type RouteHandlerReturn, type RouteHandlerReturn,
} from "@app/routing"; } from "@app/routing";
import { showError, showInfo, showSuccess } from "@app/toast"; import Cookie from "js-cookie";
import authHtml from "./login.html?raw"; import authHtml from "./login.html?raw";
import client from "@app/api"; import client from "@app/api";
import { updateUser } from "@app/auth";
import Cookie from "js-cookie";
import loggedInHtml from "./alreadyLoggedin.html?raw";
import cuteCat from "./cuteCat.png"; import cuteCat from "./cuteCat.png";
import loggedInHtml from "./alreadyLoggedin.html?raw";
import totpHtml from "./totp.html?raw";
import { isNullish } from "@app/utils"; import { isNullish } from "@app/utils";
import { showError, showInfo, showSuccess } from "@app/toast";
import { updateUser } from "@app/auth";
const TOTP_LENGTH = 6;
async function handleOtp(app: HTMLElement, token: string, returnTo: string | null) {
app.innerHTML = totpHtml;
const container = app.querySelector("#totp-container")!;
container.innerHTML = "";
const inputs: HTMLInputElement[] = [];
for (let i = 0; i < TOTP_LENGTH; i++) {
const input = document.createElement("input");
input.maxLength = 1;
input.inputMode = "numeric";
input.className =
"w-12 h-12 text-center text-xl border border-gray-300 rounded " +
"focus:outline-none focus:ring-2 focus:ring-blue-500";
container.appendChild(input);
inputs.push(input);
// Handle typing a digit
input.addEventListener("input", async () => {
const value = input.value.replace(/\D/g, "");
input.value = value;
// Auto-advance when filled
if (value && i < TOTP_LENGTH - 1) {
inputs[i + 1].focus();
}
await checkComplete();
});
// Handle backspace
input.addEventListener("keydown", (e) => {
if (e.key === "Backspace" && !input.value && i > 0) {
inputs[i - 1].focus();
}
});
// Handle pasting a full code
input.addEventListener("paste", (e: ClipboardEvent) => {
const pasted = e.clipboardData?.getData("text") ?? "";
const digits = pasted.replace(/\D/g, "").slice(0, TOTP_LENGTH);
if (digits.length > 1) {
e.preventDefault();
digits.split("").forEach((d, idx) => {
if (inputs[idx]) inputs[idx].value = d;
});
if (digits.length === TOTP_LENGTH) checkComplete();
}
});
}
// Check if all digits are entered and then call totpSend
async function checkComplete() {
const code = inputs.map((i) => i.value).join("");
if (code.length === TOTP_LENGTH && /^[0-9]+$/.test(code)) {
let res = await client.loginOtp({
loginOtpRequest: {
code, token,
}
})
if (res.kind === "success") {
Cookie.set("token", res.payload.token, {
path: "/",
sameSite: "lax",
});
if (returnTo !== null) navigateTo(returnTo);
else navigateTo("/");
}
else if (res.kind === "failed") {
showError(`Failed to authenticate: ${res.msg}`);
}
}
}
inputs[0].focus();
}
async function handleLogin( async function handleLogin(
_url: string, _url: string,
@ -67,7 +151,7 @@ async function handleLogin(
return showError( return showError(
"Error while rendering the page: no form found", "Error while rendering the page: no form found",
); );
fLogin.addEventListener("submit", async function (e: SubmitEvent) { fLogin.addEventListener("submit", async function(e: SubmitEvent) {
e.preventDefault(); e.preventDefault();
let form = e.target as HTMLFormElement | null; let form = e.target as HTMLFormElement | null;
if (form === null) return showError("Failed to send form..."); if (form === null) return showError("Failed to send form...");
@ -109,8 +193,7 @@ async function handleLogin(
break; break;
} }
case "otpRequired": { case "otpRequired": {
showInfo("Got ask OTP, not yet implemented"); return await handleOtp(app!, res.payload.token, returnTo);
break;
} }
case "failed": { case "failed": {
showError(`Failed to login: ${res.msg}`); showError(`Failed to login: ${res.msg}`);

View file

@ -0,0 +1,11 @@
<div class="grid h-full place-items-center">
<div class="bg-white shadow-lg rounded-2xl p-8 w-full max-w-md">
<h1 class="text-2xl font-semibold text-center mb-6 text-gray-800">
Welcome to <span>ft boules</span>
</h1>
<div>
<label class="block text-sm font-medium text-gray-700 mb-1">Code</label>
<div id="totp-container" class="flex gap-2 justify-center"></div>
</div>
</div>
</div>

View file

@ -0,0 +1,66 @@
<div class="grid h-full place-items-center">
<div class="bg-white shadow-lg rounded-2xl p-8 w-full max-w-md">
<h1 class="text-2xl font-semibold mb-6 text-gray-700">Edit Profile</h1>
<div id="isGuestBox" class="border-red-600 rounded-2xl border-2" hidden>
<h2 class="text-2xl font-semibold text-red-600"> This is a guest Account</h2>
<span class="text-red-600"> You can't change anything here </span>
</div>
<!-- Login Name -->
<div id="loginNameWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Login Name</label>
<div id="loginNameBox" class="font-medium mb-1 text-gray-700 rounded-sm border-2 outline-lime-100"></div>
</div>
<!-- Display Name -->
<div id="displayNameWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Display Name</label>
<input id="displayNameBox" type="text" placeholder="Display Name" name="DisplayName"
class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button id="displayNameButton" class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
Update
</button>
</div>
<!-- Password -->
<div id="passwordWrapper" class="py-2">
<label class="block font-medium mb-1 text-gray-700">Change Password</label>
<input id="passwordBox" type="password" placeholder="New Password" name="Password"
class="w-full px-4 py-2 border border-gray-300 text-gray-700 rounded-xl focus:outline-none focus:ring-2 focus:ring-blue-500" />
<button id="passwordButton" class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
Update
</button>
</div>
<!-- TOTP -->
<div class="border rounded p-4">
<h2 class="font-semibold text-lg mb-2">Two-Factor Authentication (TOTP)</h2>
<div class="flex items-center justify-between">
<span id="totpStatusText" class="font-medium text-gray-700">Status: Disabled</span>
<div class="flex gap-2">
<button id="enableTotp" type="button"
class="bg-green-600 text-white-700 px-3 py-1 rounded hover:bg-green-700">
Enable
</button>
<button id="disableTotp" type="button"
class="bg-red-600 text-white-700 px-3 py-1 rounded hover:bg-red-700 hidden">
Disable
</button>
<button id="showSecret" type="button"
class="bg-blue-600 text-white-700 px-3 py-1 rounded hover:bg-blue-700 hidden">
Show Secret
</button>
</div>
</div>
<p id="totpSecretBox" class="mt-3 text-sm bg-gray-100 border p-2 rounded hidden"></p>
</div>
</div>
</div>

View file

@ -0,0 +1,159 @@
import { addRoute, navigateTo, setTitle } from "@app/routing";
import { showError } from "@app/toast";
import page from './profile.html?raw'
import { updateUser } from "@app/auth";
import { isNullish } from "@app/utils";
import client from "@app/api";
async function route(url: string, _args: { [k: string]: string }) {
setTitle('Edit Profile')
return {
html: page, postInsert: async (app: HTMLElement | undefined) => {
const user = await updateUser();
if (isNullish(user))
return showError('No User');
if (isNullish(app))
return showError('Failed to render');
let totpState = await (async () => {
let res = await client.statusOtp();
if (res.kind === "success")
return {
enabled: (res.msg as string) === "statusOtp.success.enabled",
secret: ((res.msg as string) === "statusOtp.success.enabled") ? res.payload.secret : null,
};
else {
showError('Failed to get OTP status')
return {
enabled: false, secret: null,
}
}
})()
// ---- Simulated State ----
let totpEnabled = totpState.enabled;
let totpSecret = totpState.secret; // would come from backend
let guestBox = app.querySelector<HTMLDivElement>("#isGuestBox")!;
let displayNameWrapper = app.querySelector<HTMLDivElement>("#displayNameWrapper")!;
let displayNameBox = app.querySelector<HTMLInputElement>("#displayNameBox")!;
let displayNameButton = app.querySelector<HTMLButtonElement>("#displayNameButton")!;
let loginNameWrapper = app.querySelector<HTMLDivElement>("#loginNameWrapper")!;
let loginNameBox = app.querySelector<HTMLDivElement>("#loginNameBox")!;
let passwordWrapper = app.querySelector<HTMLDivElement>("#passwordWrapper")!;
let passwordBox = app.querySelector<HTMLInputElement>("#passwordBox")!;
let passwordButton = app.querySelector<HTMLButtonElement>("#passwordButton")!;
if (!isNullish(user.selfInfo?.loginName))
loginNameBox.innerText = user.selfInfo?.loginName;
else
loginNameBox.innerHTML = '<span class="text-red-600 font-bold mb-1">You don\'t have a login name</span>';
displayNameBox.value = user.name;
guestBox.hidden = !user.guest;
// ---- DOM Elements ----
const totpStatusText = app.querySelector("#totpStatusText")!;
const enableBtn = app.querySelector<HTMLButtonElement>("#enableTotp")!;
const disableBtn = app.querySelector<HTMLButtonElement>("#disableTotp")!;
const showSecretBtn = app.querySelector<HTMLButtonElement>("#showSecret")!;
const secretBox = app.querySelector("#totpSecretBox")!;
if (user.guest) {
for (let c of passwordButton.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
passwordButton.classList.remove(c);
}
passwordButton.disabled = true;
passwordButton.classList.add('bg-gray-700', 'hover:bg-gray-700');
passwordBox.disabled = true;
passwordBox.classList.add('color-white');
for (let c of displayNameButton.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
displayNameButton.classList.remove(c);
}
displayNameButton.disabled = true;
displayNameButton.classList.add('bg-gray-700');
displayNameButton.classList.add('color-white');
displayNameBox.disabled = true;
displayNameBox.classList.add('color-white');
for (let c of enableBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
enableBtn.classList.remove(c);
}
for (let c of disableBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
disableBtn.classList.remove(c);
}
for (let c of showSecretBtn.classList.values()) {
if (c.startsWith('bg-') || c.startsWith('hover:bg-'))
showSecretBtn.classList.remove(c);
}
enableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
disableBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
showSecretBtn.classList.add('bg-gray-700', 'hover:bg-gray-700');
enableBtn.disabled = true;
disableBtn.disabled = true;
showSecretBtn.disabled = true;
}
// ---- Update UI ----
function refreshTotpUI() {
if (totpEnabled) {
totpStatusText.textContent = "Status: Enabled";
enableBtn.classList.add("hidden");
disableBtn.classList.remove("hidden");
showSecretBtn.classList.remove("hidden");
} else {
totpStatusText.textContent = "Status: Disabled";
enableBtn.classList.remove("hidden");
disableBtn.classList.add("hidden");
showSecretBtn.classList.add("hidden");
secretBox.classList.add("hidden");
}
}
// ---- Button Events ----
enableBtn.onclick = async () => {
let res = await client.enableOtp();
if (res.kind === "success") {
navigateTo(url);
}
else {
showError(`failed to activate OTP: ${res.msg}`);
}
};
disableBtn.onclick = async () => {
let res = await client.disableOtp();
if (res.kind === "success") {
navigateTo(url);
}
else {
showError(`failed to deactivate OTP: ${res.msg}`);
}
};
showSecretBtn.onclick = () => {
secretBox.textContent = `TOTP Secret: ${totpSecret}`;
secretBox.classList.toggle("hidden");
};
// Initialize UI state
refreshTotpUI();
}
};
}
addRoute('/profile', route)

View file

@ -46,12 +46,11 @@ export class RouteHandlerData {
constructor(url: string, handler: RouteHandler, special_args: Partial<RouteHandlerSpecialArgs>) { constructor(url: string, handler: RouteHandler, special_args: Partial<RouteHandlerSpecialArgs>) {
this.special_args = Object.assign({}, RouteHandlerData.SPECIAL_ARGS_DEFAULT); this.special_args = Object.assign({}, RouteHandlerData.SPECIAL_ARGS_DEFAULT);
Object.assign(this.special_args, special_args); Object.assign(this.special_args, special_args);
console.log(url, this.special_args);
let parsed = RouteHandlerData.parseUrl(url); let parsed = RouteHandlerData.parseUrl(url);
this.handler = handler; this.handler = handler;
this.parts = parsed.parts; this.parts = parsed.parts.filter(p => p?.length !== 0);
this.url = parsed.parts.map((v, i) => v ?? `:${i}`).reduce((p, c) => `${p}/${c}`, ''); this.url = parsed.parts.filter(p => p?.length !== 0).map((v, i) => v ?? `:${i}`).reduce((p, c) => `${p}/${c}`, '');
this.args = parsed.args; this.args = parsed.args;
this.orignal_url = parsed.original; this.orignal_url = parsed.original;
} }
@ -99,7 +98,7 @@ function urlToParts(url: string): string[] {
let parts = trimed.split('/'); let parts = trimed.split('/');
if (parts.at(0) === 'app') if (parts.at(0) === 'app')
parts.shift(); parts.shift();
return parts; return parts.filter(p => p.length !== 0);
} }
function setupRoutes(): [ function setupRoutes(): [
@ -190,8 +189,6 @@ export async function handleRoute() {
} }
let user = await updateUser(); let user = await updateUser();
console.log(route_handler);
console.log(user, !route_handler.special_args.bypass_auth, user === null && !route_handler.special_args.bypass_auth);
if (user === null && !route_handler.special_args.bypass_auth) if (user === null && !route_handler.special_args.bypass_auth)
return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`) return navigateTo(`/login?returnTo=${encodeURIComponent(window.location.pathname)}`)
const app = document.getElementById('app')!; const app = document.getElementById('app')!;

View file

@ -2,8 +2,6 @@ import { escapeHTML } from "@app/utils";
import { getRoute, type RouteHandlerParams } from "@app/routing"; import { getRoute, type RouteHandlerParams } from "@app/routing";
export async function route_404(url: string, _args: RouteHandlerParams): Promise<string> { export async function route_404(url: string, _args: RouteHandlerParams): Promise<string> {
console.log(`asked about route '${url}: not found'`)
console.log(getRoute())
return ` return `
<div> 404 - Not Found </div> <div> 404 - Not Found </div>
<hr /> <hr />

View file

@ -7,6 +7,10 @@ export default defineConfig({
tailwindcss(), tailwindcss(),
tsconfigPaths(), tsconfigPaths(),
], ],
build: {
minify: false,
sourcemap: true,
},
server: { server: {
hmr: { hmr: {
protocol: 'ws', protocol: 'ws',

View file

@ -14,6 +14,7 @@ const kRouteAuthDone = Symbol('shared-route-auth-done');
type AuthedUser = { type AuthedUser = {
id: UserId; id: UserId;
name: string; name: string;
guest: boolean;
}; };
declare module 'fastify' { declare module 'fastify' {
@ -118,7 +119,7 @@ export const authPlugin = fp<{ onlySchema?: boolean }>(async (fastify, { onlySch
.clearCookie('token', { path: '/' }) .clearCookie('token', { path: '/' })
.makeResponse(401, 'notLoggedIn', 'auth.noUser'); .makeResponse(401, 'notLoggedIn', 'auth.noUser');
} }
req.authUser = { id: user.id, name: user.display_name }; req.authUser = { id: user.id, name: user.name, guest: user.guest };
} }
catch { catch {
return res return res

View file

@ -38,6 +38,32 @@
} }
} }
}, },
"400": {
"description": "Default Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"kind",
"msg"
],
"properties": {
"kind": {
"enum": [
"failure"
]
},
"msg": {
"enum": [
"disableOtp.failure.guest"
]
}
}
}
}
}
},
"401": { "401": {
"description": "Default Response", "description": "Default Response",
"content": { "content": {
@ -139,6 +165,32 @@
} }
} }
}, },
"400": {
"description": "Default Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"kind",
"msg"
],
"properties": {
"kind": {
"enum": [
"failure"
]
},
"msg": {
"enum": [
"enableOtp.failure.guest"
]
}
}
}
}
}
},
"401": { "401": {
"description": "Default Response", "description": "Default Response",
"content": { "content": {
@ -846,12 +898,12 @@
"payload": { "payload": {
"type": "object", "type": "object",
"required": [ "required": [
"url" "secret"
], ],
"properties": { "properties": {
"url": { "secret": {
"type": "string", "type": "string",
"description": "The otp url to feed into a 2fa app" "description": "The otp secret"
} }
} }
} }

View file

@ -7,6 +7,7 @@ import { typeResponse, isNullish } from '@shared/utils';
export const DisableOtpRes = { export const DisableOtpRes = {
'200': typeResponse('success', 'disableOtp.success'), '200': typeResponse('success', 'disableOtp.success'),
'500': typeResponse('failure', 'disableOtp.failure.generic'), '500': typeResponse('failure', 'disableOtp.failure.generic'),
'400': typeResponse('failure', 'disableOtp.failure.guest'),
}; };
@ -18,6 +19,13 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
async function(req, res) { async function(req, res) {
void res; void res;
if (isNullish(req.authUser)) { return res.makeResponse(500, 'failure', 'disableOtp.failure.generic'); } if (isNullish(req.authUser)) { return res.makeResponse(500, 'failure', 'disableOtp.failure.generic'); }
if (req.authUser.guest) {
return res.makeResponse(
400,
'failure',
'disableOtp.failure.guest',
);
}
this.db.deleteUserOtpSecret(req.authUser.id); this.db.deleteUserOtpSecret(req.authUser.id);
return res.makeResponse(200, 'success', 'disableOtp.success'); return res.makeResponse(200, 'success', 'disableOtp.success');
}, },

View file

@ -10,6 +10,7 @@ export const EnableOtpRes = {
url: Type.String({ description: 'The otp url to feed into a 2fa app' }), url: Type.String({ description: 'The otp url to feed into a 2fa app' }),
}), }),
'401': typeResponse('failure', ['enableOtp.failure.noUser', 'enableOtp.failure.noSecret']), '401': typeResponse('failure', ['enableOtp.failure.noUser', 'enableOtp.failure.noSecret']),
'400': typeResponse('failure', ['enableOtp.failure.guest']),
}; };
export type EnableOtpRes = MakeStaticResponse<typeof EnableOtpRes>; export type EnableOtpRes = MakeStaticResponse<typeof EnableOtpRes>;
@ -21,6 +22,13 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
{ schema: { response: EnableOtpRes, operationId: 'enableOtp' }, config: { requireAuth: true } }, { schema: { response: EnableOtpRes, operationId: 'enableOtp' }, config: { requireAuth: true } },
async function(req, res) { async function(req, res) {
if (isNullish(req.authUser)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noUser'); } if (isNullish(req.authUser)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noUser'); }
if (req.authUser.guest) {
return res.makeResponse(
400,
'failure',
'enableOtp.failure.guest',
);
}
const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id); const otpSecret = this.db.ensureUserOtpSecret(req.authUser!.id);
if (isNullish(otpSecret)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noSecret'); } if (isNullish(otpSecret)) { return res.makeResponse(403, 'failure', 'enableOtp.failure.noSecret'); }

View file

@ -2,12 +2,12 @@ import { FastifyPluginAsync } from 'fastify';
import { Type } from 'typebox'; import { Type } from 'typebox';
import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils'; import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils';
import { Otp } from '@shared/auth';
export const StatusOtpRes = { export const StatusOtpRes = {
200: Type.Union([ 200: Type.Union([
typeResponse('success', 'statusOtp.success.enabled', { url: Type.String({ description: 'The otp url to feed into a 2fa app' }) }), typeResponse('success', 'statusOtp.success.enabled', {
secret: Type.String({ description: 'The otp secret' }),
}),
typeResponse('success', 'statusOtp.success.disabled'), typeResponse('success', 'statusOtp.success.disabled'),
]), ]),
500: typeResponse('failure', 'statusOtp.failure.generic'), 500: typeResponse('failure', 'statusOtp.failure.generic'),
@ -19,13 +19,32 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
void _opts; void _opts;
fastify.get( fastify.get(
'/api/auth/statusOtp', '/api/auth/statusOtp',
{ schema: { response: StatusOtpRes, operationId: 'statusOtp' }, config: { requireAuth: true } }, {
schema: { response: StatusOtpRes, operationId: 'statusOtp' },
config: { requireAuth: true },
},
async function(req, res) { async function(req, res) {
if (isNullish(req.authUser)) { return res.makeResponse(500, 'failure', 'statusOtp.failure.generic'); } if (isNullish(req.authUser)) {
return res.makeResponse(
500,
'failure',
'statusOtp.failure.generic',
);
}
const otpSecret = this.db.getUserOtpSecret(req.authUser.id); const otpSecret = this.db.getUserOtpSecret(req.authUser.id);
if (isNullish(otpSecret)) { return res.makeResponse(200, 'success', 'statusOtp.success.disabled'); } if (isNullish(otpSecret)) {
const otp = new Otp({ secret: otpSecret }); return res.makeResponse(
return res.makeResponse(200, 'success', 'statusOtp.success.enabled', { url: otp.totpURL }); 200,
'success',
'statusOtp.success.disabled',
);
}
return res.makeResponse(
200,
'success',
'statusOtp.success.enabled',
{ secret: otpSecret },
);
}, },
); );
}; };

View file

@ -51,6 +51,32 @@
} }
} }
}, },
"400": {
"description": "Default Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"kind",
"msg"
],
"properties": {
"kind": {
"enum": [
"failure"
]
},
"msg": {
"enum": [
"disableOtp.failure.guest"
]
}
}
}
}
}
},
"401": { "401": {
"description": "Default Response", "description": "Default Response",
"content": { "content": {
@ -155,6 +181,32 @@
} }
} }
}, },
"400": {
"description": "Default Response",
"content": {
"application/json": {
"schema": {
"type": "object",
"required": [
"kind",
"msg"
],
"properties": {
"kind": {
"enum": [
"failure"
]
},
"msg": {
"enum": [
"enableOtp.failure.guest"
]
}
}
}
}
}
},
"401": { "401": {
"description": "Default Response", "description": "Default Response",
"content": { "content": {
@ -883,12 +935,12 @@
"payload": { "payload": {
"type": "object", "type": "object",
"required": [ "required": [
"url" "secret"
], ],
"properties": { "properties": {
"url": { "secret": {
"type": "string", "type": "string",
"description": "The otp url to feed into a 2fa app" "description": "The otp secret"
} }
} }
} }
@ -1069,6 +1121,20 @@
}, },
"guest": { "guest": {
"type": "boolean" "type": "boolean"
},
"selfInfo": {
"type": "object",
"properties": {
"login_name": {
"type": "string"
},
"provider_id": {
"type": "string"
},
"provider_user": {
"type": "string"
}
}
} }
} }
} }

View file

@ -72,6 +72,20 @@
}, },
"guest": { "guest": {
"type": "boolean" "type": "boolean"
},
"selfInfo": {
"type": "object",
"properties": {
"login_name": {
"type": "string"
},
"provider_id": {
"type": "string"
},
"provider_user": {
"type": "string"
}
}
} }
} }
} }

View file

@ -7,6 +7,11 @@ import { isNullish, MakeStaticResponse, typeResponse } from '@shared/utils';
export const UserInfoRes = { export const UserInfoRes = {
'200': typeResponse('success', 'userinfo.success', { '200': typeResponse('success', 'userinfo.success', {
name: Type.String(), id: Type.String(), guest: Type.Boolean(), name: Type.String(), id: Type.String(), guest: Type.Boolean(),
selfInfo: Type.Optional(Type.Object({
login_name: Type.Optional(Type.String()),
provider_id: Type.Optional(Type.String()),
provider_user: Type.Optional(Type.String()),
})),
}), }),
'403': typeResponse('failure', 'userinfo.failure.notLoggedIn'), '403': typeResponse('failure', 'userinfo.failure.notLoggedIn'),
'404': typeResponse('failure', 'userinfo.failure.unknownUser'), '404': typeResponse('failure', 'userinfo.failure.unknownUser'),
@ -38,6 +43,7 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
if (req.params.user === 'me') { if (req.params.user === 'me') {
req.params.user = req.authUser.id; req.params.user = req.authUser.id;
} }
const askSelf = req.params.user === req.authUser.id;
const user = this.db.getUser(req.params.user); const user = this.db.getUser(req.params.user);
if (isNullish(user)) { if (isNullish(user)) {
@ -57,6 +63,11 @@ const route: FastifyPluginAsync = async (fastify, _opts): Promise<void> => {
// ``` // ```
// is the same as `val = !!something` // is the same as `val = !!something`
guest: !!user.guest, guest: !!user.guest,
selfInfo: askSelf ? {
login_name: user.login,
provider_id: user.provider_name,
provider_user: user.provider_unique,
} : null,
}; };
return res.makeResponse(200, 'success', 'userinfo.success', payload); return res.makeResponse(200, 'success', 'userinfo.success', payload);