From e1132d1dd5c47ead3172dd91a972e326f2d9e63b Mon Sep 17 00:00:00 2001 From: lmeyer Date: Fri, 3 May 2024 13:00:47 +0200 Subject: [PATCH 1/5] feat(User): add User model --- packages/models/src/config/behaviors/api.ts | 2 + packages/models/src/config/config.ts | 3 + packages/models/src/user/User/User.ts | 102 ++++++ .../models/src/user/User/behaviors/api.ts | 73 ++++ .../models/src/user/User/behaviors/index.ts | 2 + .../models/src/user/User/behaviors/types.ts | 34 ++ packages/models/src/user/User/index.ts | 2 + packages/models/src/user/User/types.ts | 19 ++ packages/models/src/user/index.ts | 1 + packages/models/src/user/old/Signup.ts | 321 ++++++++++++++++++ 10 files changed, 559 insertions(+) create mode 100644 packages/models/src/user/User/User.ts create mode 100644 packages/models/src/user/User/behaviors/api.ts create mode 100644 packages/models/src/user/User/behaviors/index.ts create mode 100644 packages/models/src/user/User/behaviors/types.ts create mode 100644 packages/models/src/user/User/index.ts create mode 100644 packages/models/src/user/User/types.ts create mode 100644 packages/models/src/user/index.ts create mode 100644 packages/models/src/user/old/Signup.ts diff --git a/packages/models/src/config/behaviors/api.ts b/packages/models/src/config/behaviors/api.ts index 9fbb4efb..ffdf4aee 100644 --- a/packages/models/src/config/behaviors/api.ts +++ b/packages/models/src/config/behaviors/api.ts @@ -9,6 +9,7 @@ import { addUrlTagToProvideReactCache } from "../../react/asyncResourceInvalidat import { apiArticleBehaviors } from "../../article/Article/behaviors/index.js"; import { apiContractBehaviors } from "../../contract/Contract/behaviors/index.js"; import { apiContractItemBehaviors } from "../../contract/ContractItem/behaviors/index.js"; +import { apiUserBehaviors } from "../../user/User/behaviors/index.js"; class ApiSetupState { private _client: MittwaldAPIV2Client | undefined; @@ -29,6 +30,7 @@ class ApiSetupState { config.behaviors.customer = apiCustomerBehaviors(client); config.behaviors.ingress = apiIngressBehaviors(client); config.behaviors.appInstallation = apiAppInstallationBehaviors(client); + config.behaviors.user = apiUserBehaviors(client); config.behaviors.contract = apiContractBehaviors(client); config.behaviors.contractItem = apiContractItemBehaviors(client); } diff --git a/packages/models/src/config/config.ts b/packages/models/src/config/config.ts index c20a52c3..10cbeeb2 100644 --- a/packages/models/src/config/config.ts +++ b/packages/models/src/config/config.ts @@ -4,6 +4,7 @@ import { CustomerBehaviors } from "../customer/Customer/behaviors/index.js"; import { IngressBehaviors } from "../domain/Ingress/behaviors/index.js"; import { ContractBehaviors } from "../contract/Contract/behaviors/index.js"; import { AppInstallationBehaviors } from "../app/AppInstallation/behaviors/index.js"; +import { UserBehaviors } from "../user/User/behaviors/index.js"; import { ContractItemBehaviors } from "../contract/ContractItem/behaviors/index.js"; import { ArticleBehaviors } from "../article/Article/behaviors/index.js"; @@ -18,6 +19,7 @@ interface Config { customer: CustomerBehaviors; ingress: IngressBehaviors; appInstallation: AppInstallationBehaviors; + user: UserBehaviors; }; } @@ -32,5 +34,6 @@ export const config: Config = { customer: undefined as unknown as CustomerBehaviors, ingress: undefined as unknown as IngressBehaviors, appInstallation: undefined as unknown as AppInstallationBehaviors, + user: undefined as unknown as UserBehaviors, }, }; diff --git a/packages/models/src/user/User/User.ts b/packages/models/src/user/User/User.ts new file mode 100644 index 00000000..eb80e799 --- /dev/null +++ b/packages/models/src/user/User/User.ts @@ -0,0 +1,102 @@ +import { ReferenceModel } from "../../base/ReferenceModel.js"; +import { + type AsyncResourceVariant, + provideReact, +} from "../../lib/provideReact.js"; +import { config } from "../../config/config.js"; +import { DataModel } from "../../base/DataModel.js"; +import { classes } from "polytype"; +import { + UserAddPhoneNumberRequestData, + UserAuthenticateRequestData, + UserAuthenticateResponseData, + UserData, + UserUpdatePersonalInformationData, + UserVerifyPhoneNumberRequestData, +} from "./types.js"; +import assertObjectFound from "../../base/assertObjectFound.js"; + +export class User extends ReferenceModel { + public static ofId(id: string): User { + return new User(id); + } + + public static find = provideReact( + async (id: string): Promise => { + const data = await config.behaviors.user.find(id); + + if (data !== undefined) { + return new UserDetailed(data); + } + }, + ); + + public static get = provideReact( + async (id: string): Promise => { + const user = await this.find(id); + + assertObjectFound(user, this, id); + + return user; + }, + ); + + public static getOwn = provideReact(async (): Promise => { + return await this.get("self"); + }); + + public getDetailed = provideReact(() => + User.get(this.id), + ) as AsyncResourceVariant; + + public async updatePersonalInformation( + data: UserUpdatePersonalInformationData, + ): Promise { + await config.behaviors.user.updatePersonalInformation(this.id, data); + } + + public async addPhoneNumber( + data: UserAddPhoneNumberRequestData, + ): Promise { + await config.behaviors.user.addPhoneNumber(this.id, data); + } + + public async removePhoneNumber(): Promise { + await config.behaviors.user.removePhoneNumber(this.id); + } + + public async verifyPhoneNumber( + data: UserVerifyPhoneNumberRequestData, + ): Promise { + await config.behaviors.user.verifyPhoneNumber(this.id, data); + } + + public async requestAvatarUpload(): Promise<{ id: string }> { + return await config.behaviors.user.requestAvatarUpload(this.id); + } + + public async removeAvatar(): Promise { + await config.behaviors.user.removeAvatar(this.id); + } + + public static async authenticate( + data: UserAuthenticateRequestData, + ): Promise { + return await config.behaviors.user.authenticate(data); + } +} + +class UserCommon extends classes(DataModel, User) { + public readonly fullName: string; + + public constructor(data: UserData) { + super([data], [data.userId]); + this.fullName = `${data.person.firstName} ${data.person.lastName}`; + } +} + +export class UserDetailed extends classes(UserCommon, DataModel) { + public constructor(data: UserData) { + super([data], [data]); + } +} diff --git a/packages/models/src/user/User/behaviors/api.ts b/packages/models/src/user/User/behaviors/api.ts new file mode 100644 index 00000000..c58a6d8f --- /dev/null +++ b/packages/models/src/user/User/behaviors/api.ts @@ -0,0 +1,73 @@ +import { + assertStatus, + assertOneOfStatus, + MittwaldAPIV2Client, +} from "@mittwald/api-client"; +import { UserBehaviors } from "./types.js"; +import { UserAuthenticateRequestData } from "../types.js"; + +export const apiUserBehaviors = ( + client: MittwaldAPIV2Client, +): UserBehaviors => ({ + find: async (id) => { + const response = await client.user.getUser({ userId: id }); + + if (response.status === 200) { + return response.data; + } + assertOneOfStatus(response, [403, 404]); + }, + + updatePersonalInformation: async (id, data) => { + const response = await client.user.updatePersonalInformation({ + userId: id, + data, + }); + + assertStatus(response, 204); + }, + + addPhoneNumber: async (id, data) => { + const response = await client.user.addPhoneNumber({ userId: id, data }); + + assertStatus(response, 204); + }, + + removePhoneNumber: async (id) => { + const response = await client.user.removePhoneNumber({ userId: id }); + + assertStatus(response, 204); + }, + + verifyPhoneNumber: async (id, data) => { + const response = await client.user.verifyPhoneNumber({ userId: id, data }); + + // ToDo: 400 abfangen? + + assertStatus(response, 204); + }, + + requestAvatarUpload: async (id) => { + const response = await client.user.requestAvatarUpload({ userId: id }); + + assertStatus(response, 200); + + return { id: response.data.refId }; + }, + + removeAvatar: async (id) => { + const response = await client.user.removeAvatar({ userId: id }); + + assertStatus(response, 204); + }, + + authenticate: async (data: UserAuthenticateRequestData) => { + const response = await client.user.authenticate({ data }); + + assertOneOfStatus(response, [200, 202]); + + // ToDo: 400/401 abfangen? + + return response.data; + }, +}); diff --git a/packages/models/src/user/User/behaviors/index.ts b/packages/models/src/user/User/behaviors/index.ts new file mode 100644 index 00000000..a7b74f7c --- /dev/null +++ b/packages/models/src/user/User/behaviors/index.ts @@ -0,0 +1,2 @@ +export * from "./api.js"; +export * from "./types.js"; diff --git a/packages/models/src/user/User/behaviors/types.ts b/packages/models/src/user/User/behaviors/types.ts new file mode 100644 index 00000000..d3a9c09a --- /dev/null +++ b/packages/models/src/user/User/behaviors/types.ts @@ -0,0 +1,34 @@ +import { + UserAddPhoneNumberRequestData, + UserAuthenticateRequestData, + UserAuthenticateResponseData, + UserData, + UserUpdatePersonalInformationData, + UserVerifyPhoneNumberRequestData, +} from "../types.js"; + +export interface UserBehaviors { + find: (id: string) => Promise; + + updatePersonalInformation: ( + id: string, + data: UserUpdatePersonalInformationData, + ) => Promise; + + addPhoneNumber: ( + id: string, + data: UserAddPhoneNumberRequestData, + ) => Promise; + removePhoneNumber: (id: string) => Promise; + verifyPhoneNumber: ( + id: string, + data: UserVerifyPhoneNumberRequestData, + ) => Promise; + + requestAvatarUpload: (id: string) => Promise<{ id: string }>; + removeAvatar: (id: string) => Promise; + + authenticate: ( + data: UserAuthenticateRequestData, + ) => Promise; +} diff --git a/packages/models/src/user/User/index.ts b/packages/models/src/user/User/index.ts new file mode 100644 index 00000000..c0901e63 --- /dev/null +++ b/packages/models/src/user/User/index.ts @@ -0,0 +1,2 @@ +export * from "./User.js"; +export * from "./types.js"; diff --git a/packages/models/src/user/User/types.ts b/packages/models/src/user/User/types.ts new file mode 100644 index 00000000..970cc3d2 --- /dev/null +++ b/packages/models/src/user/User/types.ts @@ -0,0 +1,19 @@ +import { MittwaldAPIV2 } from "@mittwald/api-client"; + +export type UserData = MittwaldAPIV2.Operations.UserGetUser.ResponseData; + +export type UserUpdatePersonalInformationData = + MittwaldAPIV2.Paths.V2UsersSelfPersonalInformation.Put.Parameters.RequestBody; + +export type UserAddPhoneNumberRequestData = + MittwaldAPIV2.Paths.V2UsersUserIdPhone.Post.Parameters.RequestBody; + +export type UserVerifyPhoneNumberRequestData = + MittwaldAPIV2.Paths.V2UsersUserIdActionsVerifyPhone.Post.Parameters.RequestBody; + +export type UserAuthenticateResponseData = + | MittwaldAPIV2.Paths.V2Authenticate.Post.Responses.$200.Content.ApplicationJson + | MittwaldAPIV2.Paths.V2Authenticate.Post.Responses.$202.Content.ApplicationJson; + +export type UserAuthenticateRequestData = + MittwaldAPIV2.Paths.V2Authenticate.Post.Parameters.RequestBody; diff --git a/packages/models/src/user/index.ts b/packages/models/src/user/index.ts new file mode 100644 index 00000000..8f9829a8 --- /dev/null +++ b/packages/models/src/user/index.ts @@ -0,0 +1 @@ +export * from "./User/index.js"; diff --git a/packages/models/src/user/old/Signup.ts b/packages/models/src/user/old/Signup.ts new file mode 100644 index 00000000..fe622507 --- /dev/null +++ b/packages/models/src/user/old/Signup.ts @@ -0,0 +1,321 @@ +/* +export class Signup { + + + public static async register( + values: RegistrationInputs, + isEmailInvite?: boolean, + ): Promise { + const result = await mittwaldApi.userRegister.request({ + requestBody: { + password: values.password, + email: values.email, + person: { + title: values.person.title, + firstName: values.person.firstName, + lastName: values.person.lastName, + }, + }, + }); + + if ( + result.status === 400 && + result.content.message?.includes("email must match format") + ) { + return "invalid_string"; + } + + assertStatus(result, 201); + + store.setProfileInformation(values); + store.setUserId(result.content.userId); + store.setIsEmailInvite(!!isEmailInvite); + + return result.content.userId; + } + + public static async verifyRegistration( + values: VerifyRegistrationInputs, + email: string, + password: string, + rejectionAnimation: AnimationController, + userId?: string, + appRedirect?: CallableFunction, + ): Promise { + if (!userId) { + throw new Error("userId must be set"); + } + + const autoLogin = async (): Promise => { + const authenticationResult = await mittwaldApi.userAuthenticate.request({ + requestBody: { + email, + password, + }, + }); + if (authenticationResult.status !== 200) { + throw authenticationResult; + } + const { token } = authenticationResult.content; + sessionStore.login(token); + appRedirect && appRedirect(); + }; + + const result = await mittwaldApi.userVerifyRegistration.request({ + requestBody: { + email, + token: values.token, + userId, + }, + }); + + if (result.status === 400) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(result); + } + + assertStatus(result, 200); + + + await retryRunnable(autoLogin, { + retries: 5, + getRetryBackoff: constantRetryBackoff(2), + }); + + registerStore.clearProfileInformation(); + return; + } + + public static async verifyMfa( + values: VerifyMfaInputs, + email: string, + password: string, + rejectionAnimation: AnimationController, + ): Promise { + const result = await mittwaldApi.userAuthenticateMfa.request( + { + requestBody: { + multiFactorCode: values.multiFactorCode, + email, + password, + }, + }, + { + timeout: 30 * 1000, + }, + ); + + if (result.status !== 200) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(result); + } + + sessionStore.login(result.content.token); + sessionStore.setMfaEnabled(true); + loginStore.clearFirstFactorInformation(); + } + + public static confirmMfa = async ( + multiFactorCode: string, + rejectionAnimation: AnimationController, + ): Promise => { + const res = await mittwaldApi.userConfirmMfa.request( + { + requestBody: { multiFactorCode }, + }, + { + timeout: 30 * 1000, + }, + ); + + if (res.status !== 200) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(res); + } + + return res.content.recoveryCodesList; + }; + + public static removeMfa = async ( + multiFactorCode: string, + rejectionAnimation: AnimationController, + ): Promise => { + const res = await mittwaldApi.userDisableMfa.request( + { + requestBody: { multiFactorCode }, + }, + { + timeout: 30 * 1000, + }, + ); + + if (res.status !== 204) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(res); + } + }; + + public static resetRecoveryCodes = async ( + multiFactorCode: string, + rejectionAnimation: AnimationController, + ): Promise => { + const res = await mittwaldApi.userResetRecoverycodes.request({ + requestBody: { multiFactorCode: multiFactorCode }, + }); + + if (res.status !== 200) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(res); + } + + return res.content.recoveryCodesList; + }; + + public static verifyEmail = async ( + email: string, + token: string, + rejectionAnimation: AnimationController, + ): Promise => { + const response = await mittwaldApi.userVerifyEmail.request({ + requestBody: { + email, + token, + }, + }); + + if (response.status !== 204) { + rejectionAnimation.start(); + throw new UnexpectedResponseError(response); + } + }; + + public static useUserEmailAddress(): string { + return ( + mittwaldApi.userGetOwnAccount + .getResource({ path: { userId: "self" } }) + .useWatchData().email ?? + mittwaldApi.userGetOwnEmail.getResource({}).useWatchData().email + ); + } + + public static usePasswordUpdatedAt(): string { + return mittwaldApi.userGetPasswordUpdatedAt.getResource({}).useWatchData() + .passwordUpdatedAt; + } + + public static useMfaConfirmed(): boolean { + return mittwaldApi.userGetMfaStatus.getResource({}).useWatchData() + .confirmed; + } + + public static async updateEmailAddress( + values: UpdateEmailAddressInputs, + ): Promise { + const response = await mittwaldApi.userChangeEmail.request({ + requestBody: { + email: values.email, + }, + }); + + assertStatus(response, 204); + } + + public static async resendEmail( + values: ResendEmailInputs, + userId: string = "", + ): Promise { + const response = await mittwaldApi.userResendVerificationEmail.request({ + requestBody: { + userId, + email: values.email, + }, + }); + + assertStatus(response, 204); + } + + public static async changePassword( + values: ChangePasswordInputs, + ): Promise { + const response = await mittwaldApi.userChangePassword.request( + { + requestBody: { + oldPassword: values.oldPassword, + newPassword: values.newPassword, + multiFactorCode: values.multiFactorCode || undefined, + }, + }, + { + timeout: 30 * 1000, + }, + ); + + if (response.status === 202) { + return 202; + } + + assertStatus(response, 200); + + return response.content.token; + } + + public static async resetPassword( + values: ResetPasswordInputs, + ): Promise { + const result = await mittwaldApi.userInitPasswordReset.request({ + requestBody: { + email: values.email, + }, + }); + + assertStatus(result, 201); + } + + public static async comfirmResetPassword( + values: ConfirmPasswordResetInputs, + userId: string, + token: string, + ): Promise { + const result = await mittwaldApi.userConfirmPasswordReset.request({ + requestBody: { + userId, + token, + password: values.password, + }, + }); + + assertStatus(result, 204); + } + + public static async logout(): Promise { + const result = await mittwaldApi.userLogout.request({}); + + assertStatus(result, 204); + + sessionStore.logout(); + } + + public static useAccessTokenRetrievalKey(): AccessTokenRetrievalKey { + return mittwaldApi.userCreateAccessTokenRetrievalKey + .getResource({ path: { userId: "self" } }) + .useWatchData(); + } + + public static async delete( + values: DeleteProfileInputs, + ): Promise<409 | 400 | void> { + const response = await mittwaldApi.userDeleteUser.request({ + requestBody: { + password: values.password, + multiFactorCode: values.multiFactorCode, + }, + }); + + if (response.status === 409 || response.status === 400) { + return response.status; + } + + assertStatus(response, 200); + } +} +*/ From 4599fedf2f1ae9f8d33f218de0fce299187f565a Mon Sep 17 00:00:00 2001 From: lmeyer Date: Mon, 6 May 2024 08:21:53 +0200 Subject: [PATCH 2/5] feat(User): add User model --- packages/models/src/user/User/User.ts | 114 ++++++- .../models/src/user/User/behaviors/api.ts | 151 +++++++- .../models/src/user/User/behaviors/types.ts | 69 +++- packages/models/src/user/User/types.ts | 48 ++- packages/models/src/user/old/Signup.ts | 321 ------------------ 5 files changed, 347 insertions(+), 356 deletions(-) delete mode 100644 packages/models/src/user/old/Signup.ts diff --git a/packages/models/src/user/User/User.ts b/packages/models/src/user/User/User.ts index eb80e799..f312ca22 100644 --- a/packages/models/src/user/User/User.ts +++ b/packages/models/src/user/User/User.ts @@ -7,12 +7,24 @@ import { config } from "../../config/config.js"; import { DataModel } from "../../base/DataModel.js"; import { classes } from "polytype"; import { - UserAddPhoneNumberRequestData, + UserAuthenticateMfaRequestData, + UserAuthenticateMfaResponseData, UserAuthenticateRequestData, UserAuthenticateResponseData, + UserConfirmPasswordResetRequestData, + UserCreateAccessTokenRetrievalKeyResponseData, UserData, - UserUpdatePersonalInformationData, + UserDeleteRequestData, + UserMfaStatusData, + UserRegisterRequestData, + UserRequestAvatarUploadResponseData, + UserResendVerificationEmailRequestData, + UserUpdatePasswordRequestData, + UserUpdatePasswordResponseData, + UserUpdatePersonalInformationRequestData, + UserVerifyEmailRequestData, UserVerifyPhoneNumberRequestData, + UserVerifyRegistrationRequestData, } from "./types.js"; import assertObjectFound from "../../base/assertObjectFound.js"; @@ -49,29 +61,45 @@ export class User extends ReferenceModel { User.get(this.id), ) as AsyncResourceVariant; + public getPasswordUpdatedAt = provideReact( + async (): Promise<{ passwordUpdatedAt: string }> => { + return await config.behaviors.user.getPasswordUpdatedAt(); + }, + ); + + public getMfaStatus = provideReact(async (): Promise => { + return await config.behaviors.user.getMfaStatus(); + }); + public async updatePersonalInformation( - data: UserUpdatePersonalInformationData, + data: UserUpdatePersonalInformationRequestData, ): Promise { await config.behaviors.user.updatePersonalInformation(this.id, data); } - public async addPhoneNumber( - data: UserAddPhoneNumberRequestData, + public async addPhoneNumber(phoneNumber: string): Promise { + await config.behaviors.user.addPhoneNumber(this.id, phoneNumber); + } + + public async verifyPhoneNumber( + data: UserVerifyPhoneNumberRequestData, ): Promise { - await config.behaviors.user.addPhoneNumber(this.id, data); + await config.behaviors.user.verifyPhoneNumber(this.id, data); } public async removePhoneNumber(): Promise { await config.behaviors.user.removePhoneNumber(this.id); } - public async verifyPhoneNumber( - data: UserVerifyPhoneNumberRequestData, - ): Promise { - await config.behaviors.user.verifyPhoneNumber(this.id, data); + public async updateEmail(email: string): Promise { + await config.behaviors.user.updateEmail(email); } - public async requestAvatarUpload(): Promise<{ id: string }> { + public async verifyEmail(data: UserVerifyEmailRequestData): Promise { + await config.behaviors.user.verifyEmail(data); + } + + public async requestAvatarUpload(): Promise { return await config.behaviors.user.requestAvatarUpload(this.id); } @@ -79,11 +107,75 @@ export class User extends ReferenceModel { await config.behaviors.user.removeAvatar(this.id); } + public async updatePassword( + data: UserUpdatePasswordRequestData, + ): Promise { + return await config.behaviors.user.updatePassword(data); + } + + public async resetPassword(email: string): Promise { + await config.behaviors.user.resetPassword(email); + } + + public async confirmPasswordReset( + data: UserConfirmPasswordResetRequestData, + ): Promise { + await config.behaviors.user.confirmPasswordReset(data); + } + public static async authenticate( data: UserAuthenticateRequestData, ): Promise { return await config.behaviors.user.authenticate(data); } + + public static async register( + data: UserRegisterRequestData, + ): Promise<{ id: string }> { + return await config.behaviors.user.register(data); + } + + public static async verifyRegistration( + data: UserVerifyRegistrationRequestData, + ): Promise { + return await config.behaviors.user.verifyRegistration(data); + } + + public static async resendVerificationEmail( + data: UserResendVerificationEmailRequestData, + ): Promise { + return await config.behaviors.user.resendVerificationEmail(data); + } + + public static async delete(data: UserDeleteRequestData): Promise { + await config.behaviors.user.delete(data); + } + + public async authenticateMfa( + data: UserAuthenticateMfaRequestData, + ): Promise { + return await config.behaviors.user.authenticateMfa(data); + } + + public async confirmMfa( + multiFactorCode: string, + ): Promise<{ recoveryCodesList: string[] }> { + return await config.behaviors.user.confirmMfa(multiFactorCode); + } + + public async disableMfa(multiFactorCode: string): Promise { + await config.behaviors.user.disableMfa(multiFactorCode); + } + + public async resetRecoveryCodes( + multiFactorCode: string, + ): Promise<{ recoveryCodesList: string[] }> { + return await config.behaviors.user.resetRecoveryCodes(multiFactorCode); + } + + public async createAccessTokenRetrievalKey(): Promise { + return await config.behaviors.user.createAccessTokenRetrievalKey(); + } } class UserCommon extends classes(DataModel, User) { diff --git a/packages/models/src/user/User/behaviors/api.ts b/packages/models/src/user/User/behaviors/api.ts index c58a6d8f..c6572338 100644 --- a/packages/models/src/user/User/behaviors/api.ts +++ b/packages/models/src/user/User/behaviors/api.ts @@ -1,10 +1,18 @@ import { - assertStatus, assertOneOfStatus, + assertStatus, MittwaldAPIV2Client, } from "@mittwald/api-client"; import { UserBehaviors } from "./types.js"; -import { UserAuthenticateRequestData } from "../types.js"; +import { + UserAuthenticateMfaRequestData, + UserAuthenticateRequestData, + UserConfirmPasswordResetRequestData, + UserDeleteRequestData, + UserRegisterRequestData, + UserResendVerificationEmailRequestData, + UserVerifyRegistrationRequestData, +} from "../types.js"; export const apiUserBehaviors = ( client: MittwaldAPIV2Client, @@ -18,6 +26,22 @@ export const apiUserBehaviors = ( assertOneOfStatus(response, [403, 404]); }, + getPasswordUpdatedAt: async () => { + const response = await client.user.getPasswordUpdatedAt({}); + + assertStatus(response, 200); + + return response.data; + }, + + getMfaStatus: async () => { + const response = await client.user.getMfaStatus({}); + + assertStatus(response, 200); + + return response.data; + }, + updatePersonalInformation: async (id, data) => { const response = await client.user.updatePersonalInformation({ userId: id, @@ -27,8 +51,17 @@ export const apiUserBehaviors = ( assertStatus(response, 204); }, - addPhoneNumber: async (id, data) => { - const response = await client.user.addPhoneNumber({ userId: id, data }); + addPhoneNumber: async (id, phoneNumber) => { + const response = await client.user.addPhoneNumber({ + userId: id, + data: { phoneNumber }, + }); + + assertStatus(response, 204); + }, + + verifyPhoneNumber: async (id, data) => { + const response = await client.user.verifyPhoneNumber({ userId: id, data }); assertStatus(response, 204); }, @@ -39,10 +72,14 @@ export const apiUserBehaviors = ( assertStatus(response, 204); }, - verifyPhoneNumber: async (id, data) => { - const response = await client.user.verifyPhoneNumber({ userId: id, data }); + updateEmail: async (email) => { + const response = await client.user.changeEmail({ data: { email } }); + + assertStatus(response, 204); + }, - // ToDo: 400 abfangen? + verifyEmail: async (data) => { + const response = await client.user.verifyEmail({ data }); assertStatus(response, 204); }, @@ -52,7 +89,7 @@ export const apiUserBehaviors = ( assertStatus(response, 200); - return { id: response.data.refId }; + return response.data; }, removeAvatar: async (id) => { @@ -61,12 +98,108 @@ export const apiUserBehaviors = ( assertStatus(response, 204); }, + updatePassword: async (data) => { + const response = await client.user.changePassword({ data }); + + assertStatus(response, 200); + + return response.data; + }, + + resetPassword: async (email) => { + const response = await client.user.initPasswordReset({ data: { email } }); + + assertStatus(response, 201); + }, + + confirmPasswordReset: async (data: UserConfirmPasswordResetRequestData) => { + const response = await client.user.confirmPasswordReset({ data }); + + assertStatus(response, 204); + }, + authenticate: async (data: UserAuthenticateRequestData) => { const response = await client.user.authenticate({ data }); assertOneOfStatus(response, [200, 202]); - // ToDo: 400/401 abfangen? + return response.data; + }, + + logout: async () => { + const response = await client.user.logout(); + + assertStatus(response, 204); + }, + + register: async (data: UserRegisterRequestData) => { + const response = await client.user.register({ data }); + + assertStatus(response, 201); + + return { id: response.data.userId }; + }, + + verifyRegistration: async (data: UserVerifyRegistrationRequestData) => { + const response = await client.user.verifyRegistration({ data }); + + assertStatus(response, 200); + }, + + resendVerificationEmail: async ( + data: UserResendVerificationEmailRequestData, + ) => { + const response = await client.user.resendVerificationEmail({ data }); + + assertStatus(response, 204); + }, + + delete: async (data: UserDeleteRequestData) => { + const response = await client.user.deleteUser({ data }); + + assertOneOfStatus(response, [200, 202]); + }, + + authenticateMfa: async (data: UserAuthenticateMfaRequestData) => { + const response = await client.user.authenticateMfa({ data }); + + assertStatus(response, 200); + + return response.data; + }, + + confirmMfa: async (multiFactorCode: string) => { + const response = await client.user.confirmMfa({ + data: { multiFactorCode }, + }); + + assertStatus(response, 200); + + return response.data; + }, + + disableMfa: async (multiFactorCode: string) => { + const response = await client.user.disableMfa({ + data: { multiFactorCode }, + }); + + assertStatus(response, 204); + }, + + resetRecoveryCodes: async (multiFactorCode: string) => { + const response = await client.user.resetRecoverycodes({ + data: { multiFactorCode }, + }); + + assertStatus(response, 200); + + return response.data; + }, + + createAccessTokenRetrievalKey: async () => { + const response = await client.user.createAccessTokenRetrievalKey(); + + assertStatus(response, 201); return response.data; }, diff --git a/packages/models/src/user/User/behaviors/types.ts b/packages/models/src/user/User/behaviors/types.ts index d3a9c09a..0fa44612 100644 --- a/packages/models/src/user/User/behaviors/types.ts +++ b/packages/models/src/user/User/behaviors/types.ts @@ -1,34 +1,85 @@ import { - UserAddPhoneNumberRequestData, + UserAuthenticateMfaRequestData, + UserAuthenticateMfaResponseData, UserAuthenticateRequestData, UserAuthenticateResponseData, + UserConfirmPasswordResetRequestData, + UserCreateAccessTokenRetrievalKeyResponseData, UserData, - UserUpdatePersonalInformationData, + UserDeleteRequestData, + UserMfaStatusData, + UserRegisterRequestData, + UserRequestAvatarUploadResponseData, + UserResendVerificationEmailRequestData, + UserUpdatePasswordRequestData, + UserUpdatePasswordResponseData, + UserUpdatePersonalInformationRequestData, + UserVerifyEmailRequestData, UserVerifyPhoneNumberRequestData, + UserVerifyRegistrationRequestData, } from "../types.js"; export interface UserBehaviors { find: (id: string) => Promise; + getPasswordUpdatedAt: () => Promise<{ passwordUpdatedAt: string }>; + + getMfaStatus: () => Promise; + updatePersonalInformation: ( id: string, - data: UserUpdatePersonalInformationData, + data: UserUpdatePersonalInformationRequestData, ) => Promise; - addPhoneNumber: ( - id: string, - data: UserAddPhoneNumberRequestData, - ) => Promise; - removePhoneNumber: (id: string) => Promise; + addPhoneNumber: (id: string, phoneNumber: string) => Promise; verifyPhoneNumber: ( id: string, data: UserVerifyPhoneNumberRequestData, ) => Promise; + removePhoneNumber: (id: string) => Promise; + + updateEmail: (email: string) => Promise; + verifyEmail: (data: UserVerifyEmailRequestData) => Promise; - requestAvatarUpload: (id: string) => Promise<{ id: string }>; + requestAvatarUpload: ( + id: string, + ) => Promise; removeAvatar: (id: string) => Promise; + updatePassword: ( + data: UserUpdatePasswordRequestData, + ) => Promise; + + resetPassword: (email: string) => Promise; + confirmPasswordReset: ( + data: UserConfirmPasswordResetRequestData, + ) => Promise; + authenticate: ( data: UserAuthenticateRequestData, ) => Promise; + logout: () => Promise; + + register: (data: UserRegisterRequestData) => Promise<{ id: string }>; + verifyRegistration: ( + data: UserVerifyRegistrationRequestData, + ) => Promise; + resendVerificationEmail: ( + data: UserResendVerificationEmailRequestData, + ) => Promise; + + delete: (data: UserDeleteRequestData) => Promise; + + authenticateMfa: ( + data: UserAuthenticateMfaRequestData, + ) => Promise; + confirmMfa: ( + multiFactorCode: string, + ) => Promise<{ recoveryCodesList: string[] }>; + disableMfa: (multiFactorCode: string) => Promise; + resetRecoveryCodes: ( + multiFactorCode: string, + ) => Promise<{ recoveryCodesList: string[] }>; + + createAccessTokenRetrievalKey: () => Promise; } diff --git a/packages/models/src/user/User/types.ts b/packages/models/src/user/User/types.ts index 970cc3d2..3c94ea77 100644 --- a/packages/models/src/user/User/types.ts +++ b/packages/models/src/user/User/types.ts @@ -2,18 +2,54 @@ import { MittwaldAPIV2 } from "@mittwald/api-client"; export type UserData = MittwaldAPIV2.Operations.UserGetUser.ResponseData; -export type UserUpdatePersonalInformationData = - MittwaldAPIV2.Paths.V2UsersSelfPersonalInformation.Put.Parameters.RequestBody; +export type UserMfaStatusData = + MittwaldAPIV2.Operations.UserGetMfaStatus.ResponseData; -export type UserAddPhoneNumberRequestData = - MittwaldAPIV2.Paths.V2UsersUserIdPhone.Post.Parameters.RequestBody; +export type UserUpdatePersonalInformationRequestData = + MittwaldAPIV2.Paths.V2UsersSelfPersonalInformation.Put.Parameters.RequestBody; export type UserVerifyPhoneNumberRequestData = MittwaldAPIV2.Paths.V2UsersUserIdActionsVerifyPhone.Post.Parameters.RequestBody; +export type UserRequestAvatarUploadResponseData = + MittwaldAPIV2.Paths.V2UsersUserIdAvatar.Post.Parameters.RequestBody; + +export type UserVerifyEmailRequestData = + MittwaldAPIV2.Paths.V2UsersSelfCredentialsEmailActionsVerifyEmail.Post.Parameters.RequestBody; + +export type UserUpdatePasswordRequestData = + MittwaldAPIV2.Paths.V2UsersSelfCredentialsPassword.Put.Parameters.RequestBody; + +export type UserUpdatePasswordResponseData = + MittwaldAPIV2.Paths.V2UsersSelfCredentialsPassword.Put.Responses.$200.Content.ApplicationJson; + +export type UserConfirmPasswordResetRequestData = + MittwaldAPIV2.Paths.V2UsersSelfCredentialsPasswordConfirmReset.Post.Parameters.RequestBody; + +export type UserAuthenticateRequestData = + MittwaldAPIV2.Paths.V2Authenticate.Post.Parameters.RequestBody; + export type UserAuthenticateResponseData = | MittwaldAPIV2.Paths.V2Authenticate.Post.Responses.$200.Content.ApplicationJson | MittwaldAPIV2.Paths.V2Authenticate.Post.Responses.$202.Content.ApplicationJson; -export type UserAuthenticateRequestData = - MittwaldAPIV2.Paths.V2Authenticate.Post.Parameters.RequestBody; +export type UserRegisterRequestData = + MittwaldAPIV2.Paths.V2Register.Post.Parameters.RequestBody; + +export type UserVerifyRegistrationRequestData = + MittwaldAPIV2.Paths.V2VerifyRegistration.Post.Parameters.RequestBody; + +export type UserResendVerificationEmailRequestData = + MittwaldAPIV2.Paths.V2UsersSelfCredentialsEmailActionsResendEmail.Post.Parameters.RequestBody; + +export type UserDeleteRequestData = + MittwaldAPIV2.Paths.V2UsersSelf.Delete.Parameters.RequestBody; + +export type UserAuthenticateMfaRequestData = + MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Parameters.RequestBody; + +export type UserAuthenticateMfaResponseData = + MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Responses.$200.Content.ApplicationJson; + +export type UserCreateAccessTokenRetrievalKeyResponseData = + MittwaldAPIV2.Paths.V2UsersSelfTokenRetrievalKey.Post.Responses.$201.Content.ApplicationJson; diff --git a/packages/models/src/user/old/Signup.ts b/packages/models/src/user/old/Signup.ts deleted file mode 100644 index fe622507..00000000 --- a/packages/models/src/user/old/Signup.ts +++ /dev/null @@ -1,321 +0,0 @@ -/* -export class Signup { - - - public static async register( - values: RegistrationInputs, - isEmailInvite?: boolean, - ): Promise { - const result = await mittwaldApi.userRegister.request({ - requestBody: { - password: values.password, - email: values.email, - person: { - title: values.person.title, - firstName: values.person.firstName, - lastName: values.person.lastName, - }, - }, - }); - - if ( - result.status === 400 && - result.content.message?.includes("email must match format") - ) { - return "invalid_string"; - } - - assertStatus(result, 201); - - store.setProfileInformation(values); - store.setUserId(result.content.userId); - store.setIsEmailInvite(!!isEmailInvite); - - return result.content.userId; - } - - public static async verifyRegistration( - values: VerifyRegistrationInputs, - email: string, - password: string, - rejectionAnimation: AnimationController, - userId?: string, - appRedirect?: CallableFunction, - ): Promise { - if (!userId) { - throw new Error("userId must be set"); - } - - const autoLogin = async (): Promise => { - const authenticationResult = await mittwaldApi.userAuthenticate.request({ - requestBody: { - email, - password, - }, - }); - if (authenticationResult.status !== 200) { - throw authenticationResult; - } - const { token } = authenticationResult.content; - sessionStore.login(token); - appRedirect && appRedirect(); - }; - - const result = await mittwaldApi.userVerifyRegistration.request({ - requestBody: { - email, - token: values.token, - userId, - }, - }); - - if (result.status === 400) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(result); - } - - assertStatus(result, 200); - - - await retryRunnable(autoLogin, { - retries: 5, - getRetryBackoff: constantRetryBackoff(2), - }); - - registerStore.clearProfileInformation(); - return; - } - - public static async verifyMfa( - values: VerifyMfaInputs, - email: string, - password: string, - rejectionAnimation: AnimationController, - ): Promise { - const result = await mittwaldApi.userAuthenticateMfa.request( - { - requestBody: { - multiFactorCode: values.multiFactorCode, - email, - password, - }, - }, - { - timeout: 30 * 1000, - }, - ); - - if (result.status !== 200) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(result); - } - - sessionStore.login(result.content.token); - sessionStore.setMfaEnabled(true); - loginStore.clearFirstFactorInformation(); - } - - public static confirmMfa = async ( - multiFactorCode: string, - rejectionAnimation: AnimationController, - ): Promise => { - const res = await mittwaldApi.userConfirmMfa.request( - { - requestBody: { multiFactorCode }, - }, - { - timeout: 30 * 1000, - }, - ); - - if (res.status !== 200) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(res); - } - - return res.content.recoveryCodesList; - }; - - public static removeMfa = async ( - multiFactorCode: string, - rejectionAnimation: AnimationController, - ): Promise => { - const res = await mittwaldApi.userDisableMfa.request( - { - requestBody: { multiFactorCode }, - }, - { - timeout: 30 * 1000, - }, - ); - - if (res.status !== 204) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(res); - } - }; - - public static resetRecoveryCodes = async ( - multiFactorCode: string, - rejectionAnimation: AnimationController, - ): Promise => { - const res = await mittwaldApi.userResetRecoverycodes.request({ - requestBody: { multiFactorCode: multiFactorCode }, - }); - - if (res.status !== 200) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(res); - } - - return res.content.recoveryCodesList; - }; - - public static verifyEmail = async ( - email: string, - token: string, - rejectionAnimation: AnimationController, - ): Promise => { - const response = await mittwaldApi.userVerifyEmail.request({ - requestBody: { - email, - token, - }, - }); - - if (response.status !== 204) { - rejectionAnimation.start(); - throw new UnexpectedResponseError(response); - } - }; - - public static useUserEmailAddress(): string { - return ( - mittwaldApi.userGetOwnAccount - .getResource({ path: { userId: "self" } }) - .useWatchData().email ?? - mittwaldApi.userGetOwnEmail.getResource({}).useWatchData().email - ); - } - - public static usePasswordUpdatedAt(): string { - return mittwaldApi.userGetPasswordUpdatedAt.getResource({}).useWatchData() - .passwordUpdatedAt; - } - - public static useMfaConfirmed(): boolean { - return mittwaldApi.userGetMfaStatus.getResource({}).useWatchData() - .confirmed; - } - - public static async updateEmailAddress( - values: UpdateEmailAddressInputs, - ): Promise { - const response = await mittwaldApi.userChangeEmail.request({ - requestBody: { - email: values.email, - }, - }); - - assertStatus(response, 204); - } - - public static async resendEmail( - values: ResendEmailInputs, - userId: string = "", - ): Promise { - const response = await mittwaldApi.userResendVerificationEmail.request({ - requestBody: { - userId, - email: values.email, - }, - }); - - assertStatus(response, 204); - } - - public static async changePassword( - values: ChangePasswordInputs, - ): Promise { - const response = await mittwaldApi.userChangePassword.request( - { - requestBody: { - oldPassword: values.oldPassword, - newPassword: values.newPassword, - multiFactorCode: values.multiFactorCode || undefined, - }, - }, - { - timeout: 30 * 1000, - }, - ); - - if (response.status === 202) { - return 202; - } - - assertStatus(response, 200); - - return response.content.token; - } - - public static async resetPassword( - values: ResetPasswordInputs, - ): Promise { - const result = await mittwaldApi.userInitPasswordReset.request({ - requestBody: { - email: values.email, - }, - }); - - assertStatus(result, 201); - } - - public static async comfirmResetPassword( - values: ConfirmPasswordResetInputs, - userId: string, - token: string, - ): Promise { - const result = await mittwaldApi.userConfirmPasswordReset.request({ - requestBody: { - userId, - token, - password: values.password, - }, - }); - - assertStatus(result, 204); - } - - public static async logout(): Promise { - const result = await mittwaldApi.userLogout.request({}); - - assertStatus(result, 204); - - sessionStore.logout(); - } - - public static useAccessTokenRetrievalKey(): AccessTokenRetrievalKey { - return mittwaldApi.userCreateAccessTokenRetrievalKey - .getResource({ path: { userId: "self" } }) - .useWatchData(); - } - - public static async delete( - values: DeleteProfileInputs, - ): Promise<409 | 400 | void> { - const response = await mittwaldApi.userDeleteUser.request({ - requestBody: { - password: values.password, - multiFactorCode: values.multiFactorCode, - }, - }); - - if (response.status === 409 || response.status === 400) { - return response.status; - } - - assertStatus(response, 200); - } -} -*/ From 55c58dd33ac3e79a8c32de326d8f8e445d769438 Mon Sep 17 00:00:00 2001 From: lmeyer Date: Thu, 19 Sep 2024 07:53:08 +0200 Subject: [PATCH 3/5] update user model --- packages/models/src/user/User/User.ts | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/packages/models/src/user/User/User.ts b/packages/models/src/user/User/User.ts index f312ca22..23117f21 100644 --- a/packages/models/src/user/User/User.ts +++ b/packages/models/src/user/User/User.ts @@ -1,10 +1,8 @@ -import { ReferenceModel } from "../../base/ReferenceModel.js"; import { type AsyncResourceVariant, provideReact, } from "../../lib/provideReact.js"; import { config } from "../../config/config.js"; -import { DataModel } from "../../base/DataModel.js"; import { classes } from "polytype"; import { UserAuthenticateMfaRequestData, @@ -27,6 +25,7 @@ import { UserVerifyRegistrationRequestData, } from "./types.js"; import assertObjectFound from "../../base/assertObjectFound.js"; +import { DataModel, ReferenceModel } from "../../base/index.js"; export class User extends ReferenceModel { public static ofId(id: string): User { @@ -36,7 +35,6 @@ export class User extends ReferenceModel { public static find = provideReact( async (id: string): Promise => { const data = await config.behaviors.user.find(id); - if (data !== undefined) { return new UserDetailed(data); } @@ -46,9 +44,7 @@ export class User extends ReferenceModel { public static get = provideReact( async (id: string): Promise => { const user = await this.find(id); - assertObjectFound(user, this, id); - return user; }, ); @@ -57,10 +53,16 @@ export class User extends ReferenceModel { return await this.get("self"); }); - public getDetailed = provideReact(() => - User.get(this.id), + public getDetailed = provideReact( + () => User.get(this.id), + [this.id], ) as AsyncResourceVariant; + public findDetailed = provideReact( + () => User.find(this.id), + [this.id], + ) as AsyncResourceVariant; + public getPasswordUpdatedAt = provideReact( async (): Promise<{ passwordUpdatedAt: string }> => { return await config.behaviors.user.getPasswordUpdatedAt(); From b16752caee8a914b913e8320c09d0b3326c59cfe Mon Sep 17 00:00:00 2001 From: lmeyer Date: Thu, 19 Sep 2024 08:01:42 +0200 Subject: [PATCH 4/5] update user model --- packages/models/src/user/User/User.ts | 5 ----- packages/models/src/user/User/behaviors/api.ts | 8 -------- packages/models/src/user/User/behaviors/types.ts | 3 --- packages/models/src/user/User/types.ts | 3 --- 4 files changed, 19 deletions(-) diff --git a/packages/models/src/user/User/User.ts b/packages/models/src/user/User/User.ts index 23117f21..e28b66db 100644 --- a/packages/models/src/user/User/User.ts +++ b/packages/models/src/user/User/User.ts @@ -10,7 +10,6 @@ import { UserAuthenticateRequestData, UserAuthenticateResponseData, UserConfirmPasswordResetRequestData, - UserCreateAccessTokenRetrievalKeyResponseData, UserData, UserDeleteRequestData, UserMfaStatusData, @@ -174,10 +173,6 @@ export class User extends ReferenceModel { ): Promise<{ recoveryCodesList: string[] }> { return await config.behaviors.user.resetRecoveryCodes(multiFactorCode); } - - public async createAccessTokenRetrievalKey(): Promise { - return await config.behaviors.user.createAccessTokenRetrievalKey(); - } } class UserCommon extends classes(DataModel, User) { diff --git a/packages/models/src/user/User/behaviors/api.ts b/packages/models/src/user/User/behaviors/api.ts index c6572338..6a4b7da8 100644 --- a/packages/models/src/user/User/behaviors/api.ts +++ b/packages/models/src/user/User/behaviors/api.ts @@ -195,12 +195,4 @@ export const apiUserBehaviors = ( return response.data; }, - - createAccessTokenRetrievalKey: async () => { - const response = await client.user.createAccessTokenRetrievalKey(); - - assertStatus(response, 201); - - return response.data; - }, }); diff --git a/packages/models/src/user/User/behaviors/types.ts b/packages/models/src/user/User/behaviors/types.ts index 0fa44612..ee584375 100644 --- a/packages/models/src/user/User/behaviors/types.ts +++ b/packages/models/src/user/User/behaviors/types.ts @@ -4,7 +4,6 @@ import { UserAuthenticateRequestData, UserAuthenticateResponseData, UserConfirmPasswordResetRequestData, - UserCreateAccessTokenRetrievalKeyResponseData, UserData, UserDeleteRequestData, UserMfaStatusData, @@ -80,6 +79,4 @@ export interface UserBehaviors { resetRecoveryCodes: ( multiFactorCode: string, ) => Promise<{ recoveryCodesList: string[] }>; - - createAccessTokenRetrievalKey: () => Promise; } diff --git a/packages/models/src/user/User/types.ts b/packages/models/src/user/User/types.ts index 3c94ea77..daf0b272 100644 --- a/packages/models/src/user/User/types.ts +++ b/packages/models/src/user/User/types.ts @@ -50,6 +50,3 @@ export type UserAuthenticateMfaRequestData = export type UserAuthenticateMfaResponseData = MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Responses.$200.Content.ApplicationJson; - -export type UserCreateAccessTokenRetrievalKeyResponseData = - MittwaldAPIV2.Paths.V2UsersSelfTokenRetrievalKey.Post.Responses.$201.Content.ApplicationJson; From cb72bd7c2a13e8e842be696cb653c579fe9c4de5 Mon Sep 17 00:00:00 2001 From: Marco Falkenberg Date: Fri, 27 Sep 2024 09:42:45 +0200 Subject: [PATCH 5/5] refact(Model): add separate model for MFA --- .pnp.cjs | 2 + packages/models/package.json | 2 + packages/models/src/auth/Mfa/Mfa.ts | 51 +++++++++++++++ .../src/auth/Mfa/PendingMfaAuthentication.ts | 26 ++++++++ packages/models/src/auth/Mfa/behaviors/api.ts | 64 +++++++++++++++++++ .../models/src/auth/Mfa/behaviors/index.ts | 2 + .../models/src/auth/Mfa/behaviors/types.ts | 24 +++++++ packages/models/src/auth/Mfa/index.ts | 1 + packages/models/src/auth/Mfa/types.ts | 20 ++++++ .../src/auth/RecoveryCodes/RecoveryCodes.ts | 11 ++++ .../models/src/auth/RecoveryCodes/index.ts | 1 + .../models/src/auth/RecoveryCodes/types.ts | 3 + packages/models/src/auth/Session/Session.ts | 20 ++++++ packages/models/src/auth/Session/index.ts | 1 + packages/models/src/auth/Session/types.ts | 5 ++ packages/models/src/config/behaviors/api.ts | 2 + packages/models/src/config/config.ts | 3 + packages/models/src/user/User/User.ts | 49 ++++---------- .../models/src/user/User/behaviors/api.ts | 45 ------------- .../models/src/user/User/behaviors/types.ts | 17 +---- packages/models/src/user/User/types.ts | 13 +--- yarn.lock | 6 +- 22 files changed, 257 insertions(+), 111 deletions(-) create mode 100644 packages/models/src/auth/Mfa/Mfa.ts create mode 100644 packages/models/src/auth/Mfa/PendingMfaAuthentication.ts create mode 100644 packages/models/src/auth/Mfa/behaviors/api.ts create mode 100644 packages/models/src/auth/Mfa/behaviors/index.ts create mode 100644 packages/models/src/auth/Mfa/behaviors/types.ts create mode 100644 packages/models/src/auth/Mfa/index.ts create mode 100644 packages/models/src/auth/Mfa/types.ts create mode 100644 packages/models/src/auth/RecoveryCodes/RecoveryCodes.ts create mode 100644 packages/models/src/auth/RecoveryCodes/index.ts create mode 100644 packages/models/src/auth/RecoveryCodes/types.ts create mode 100644 packages/models/src/auth/Session/Session.ts create mode 100644 packages/models/src/auth/Session/index.ts create mode 100644 packages/models/src/auth/Session/types.ts diff --git a/.pnp.cjs b/.pnp.cjs index 852300dc..ab1b6b30 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -2174,6 +2174,7 @@ const RAW_RUNTIME_STATE = ["@testing-library/react", "virtual:e06e30328889a833dfcbfbc5c33f5e1173628bc1414934ce897e2f1b90b91087fa3f8b59a8a3ced3a53b1f7df474adc821437919c179e8897ac6cecf6bf31f00#npm:16.0.1"],\ ["@types/dinero.js", "npm:1.9.4"],\ ["@types/jest", "npm:29.5.12"],\ + ["@types/luxon", "npm:3.4.2"],\ ["@types/react", "npm:18.3.3"],\ ["@types/react-dom", "npm:18.3.0"],\ ["@typescript-eslint/eslint-plugin", "virtual:36a01d8083315b8a6e8362097258ea8bc0f9dfb672cb210742e054760850c673a1038f542a6b7156397b5275ace8ee0482231cac5e8898044fa1a1c29f78ee5b#npm:7.18.0"],\ @@ -2187,6 +2188,7 @@ const RAW_RUNTIME_STATE = ["eslint-plugin-prettier", "virtual:36a01d8083315b8a6e8362097258ea8bc0f9dfb672cb210742e054760850c673a1038f542a6b7156397b5275ace8ee0482231cac5e8898044fa1a1c29f78ee5b#npm:5.2.1"],\ ["jest", "virtual:b2e857f8c518119e848cf4ef51cff2bf36fb4db0f8e551e1de9a65b88f5466b35ebea1913543d6258bb39baec552d66e8e4c2e8ae0858f2f3f9bf35009befb70#npm:29.7.0"],\ ["jest-environment-jsdom", "virtual:e06e30328889a833dfcbfbc5c33f5e1173628bc1414934ce897e2f1b90b91087fa3f8b59a8a3ced3a53b1f7df474adc821437919c179e8897ac6cecf6bf31f00#npm:29.7.0"],\ + ["luxon", "npm:3.5.0"],\ ["object-code", "npm:1.3.3"],\ ["polytype", "npm:0.17.0"],\ ["prettier", "npm:3.3.3"],\ diff --git a/packages/models/package.json b/packages/models/package.json index 86050fde..2eef080c 100644 --- a/packages/models/package.json +++ b/packages/models/package.json @@ -43,6 +43,7 @@ "another-deep-freeze": "^1.0.0", "context": "^3.0.31", "dinero.js": "^1.9.1", + "luxon": "^3.5.0", "object-code": "^1.3.3", "polytype": "^0.17.0", "tsd": "^0.31.2", @@ -56,6 +57,7 @@ "@testing-library/react": "^16.0.1", "@types/dinero.js": "^1", "@types/jest": "^29.5.12", + "@types/luxon": "^3", "@types/react": "^18.3.3", "@types/react-dom": "^18", "@typescript-eslint/eslint-plugin": "^7.18.0", diff --git a/packages/models/src/auth/Mfa/Mfa.ts b/packages/models/src/auth/Mfa/Mfa.ts new file mode 100644 index 00000000..24ea000d --- /dev/null +++ b/packages/models/src/auth/Mfa/Mfa.ts @@ -0,0 +1,51 @@ +import { DataModel } from "../../base/index.js"; +import { MfaStatusData, RecoverMfaRequestData } from "./types.js"; +import { provideReact } from "../../react/index.js"; +import { config } from "../../config/config.js"; +import { RecoveryCodes } from "../RecoveryCodes/RecoveryCodes.js"; + +export class Mfa { + public static getStatus = provideReact( + async (): Promise => { + const data = await config.behaviors.mfa.getStatus(); + return new MfaStatus(data); + }, + ); + + public static async confirm(multiFactorCode: string): Promise { + const response = await config.behaviors.mfa.confirm(multiFactorCode); + return new RecoveryCodes({ + codes: response.recoveryCodesList, + }); + } + + public static async disable(multiFactorCode: string): Promise { + await config.behaviors.mfa.disable(multiFactorCode); + } + + public static async recover(data: RecoverMfaRequestData): Promise { + await config.behaviors.mfa.recover(data); + } + + public static async resetRecoveryCodes( + multiFactorCode: string, + ): Promise { + const response = + await config.behaviors.mfa.resetRecoveryCodes(multiFactorCode); + + return new RecoveryCodes({ + codes: response.recoveryCodesList, + }); + } +} + +export class MfaStatus extends DataModel { + public readonly isInitialized; + public readonly isConfirmed; + + public constructor(data: MfaStatusData) { + super(data); + this.isConfirmed = data.confirmed; + this.isInitialized = data.initialized; + } +} diff --git a/packages/models/src/auth/Mfa/PendingMfaAuthentication.ts b/packages/models/src/auth/Mfa/PendingMfaAuthentication.ts new file mode 100644 index 00000000..7a1795ef --- /dev/null +++ b/packages/models/src/auth/Mfa/PendingMfaAuthentication.ts @@ -0,0 +1,26 @@ +import { UserAuthenticateRequestData } from "../../user/index.js"; +import { config } from "../../config/config.js"; +import { Session } from "../Session/index.js"; + +export class PendingMfaAuthentication { + private authenticationRequestData: UserAuthenticateRequestData | undefined; + + public constructor(authenticationRequestData: UserAuthenticateRequestData) { + this.authenticationRequestData = authenticationRequestData; + } + + public async provideMultiFactorCode(multiFactorCode: string) { + const sessionData = await config.behaviors.mfa.authenticateMfa({ + ...this.authenticationRequestData, + multiFactorCode, + }); + + this.authenticationRequestData = undefined; + + return new Session(sessionData); + } + + public get isAlreadyConfirmed() { + return !this.authenticationRequestData; + } +} diff --git a/packages/models/src/auth/Mfa/behaviors/api.ts b/packages/models/src/auth/Mfa/behaviors/api.ts new file mode 100644 index 00000000..b93021a3 --- /dev/null +++ b/packages/models/src/auth/Mfa/behaviors/api.ts @@ -0,0 +1,64 @@ +import { assertStatus, MittwaldAPIV2Client } from "@mittwald/api-client"; +import { MfaBehaviors } from "./types.js"; + +export const apiMfaBehaviors = (client: MittwaldAPIV2Client): MfaBehaviors => ({ + authenticateMfa: async (data) => { + const response = await client.user.authenticateMfa({ + data, + }); + + assertStatus(response, 200); + + return response.data; + }, + + getStatus: async () => { + const response = await client.user.getMfaStatus({}); + + assertStatus(response, 200); + + return response.data; + }, + + recover: async (data) => { + const { recoveryCode, ...restData } = data; + const response = await client.user.authenticateMfa({ + data: { + multiFactorCode: recoveryCode, + ...restData, + }, + }); + + assertStatus(response, 200); + + return response.data; + }, + + confirm: async (multiFactorCode: string) => { + const response = await client.user.confirmMfa({ + data: { multiFactorCode }, + }); + + assertStatus(response, 200); + + return response.data; + }, + + resetRecoveryCodes: async (multiFactorCode: string) => { + const response = await client.user.resetRecoverycodes({ + data: { multiFactorCode }, + }); + + assertStatus(response, 200); + + return response.data; + }, + + disable: async (multiFactorCode: string) => { + const response = await client.user.disableMfa({ + data: { multiFactorCode }, + }); + + assertStatus(response, 204); + }, +}); diff --git a/packages/models/src/auth/Mfa/behaviors/index.ts b/packages/models/src/auth/Mfa/behaviors/index.ts new file mode 100644 index 00000000..a7b74f7c --- /dev/null +++ b/packages/models/src/auth/Mfa/behaviors/index.ts @@ -0,0 +1,2 @@ +export * from "./api.js"; +export * from "./types.js"; diff --git a/packages/models/src/auth/Mfa/behaviors/types.ts b/packages/models/src/auth/Mfa/behaviors/types.ts new file mode 100644 index 00000000..1ae5959a --- /dev/null +++ b/packages/models/src/auth/Mfa/behaviors/types.ts @@ -0,0 +1,24 @@ +import { + AuthenticateMfaRequestData, + ConfirmMfaResponseData, + MfaStatusData, + RecoverMfaRequestData, + ResetMfaRecoveryResponseData, +} from "../types.js"; +import { SessionData } from "../../Session/types.js"; + +export interface MfaBehaviors { + getStatus: () => Promise; + + recover: (data: RecoverMfaRequestData) => Promise; + + resetRecoveryCodes: ( + multiFactorCode: string, + ) => Promise; + + confirm: (multiFactorCode: string) => Promise; + + disable: (multiFactorCode: string) => Promise; + + authenticateMfa: (data: AuthenticateMfaRequestData) => Promise; +} diff --git a/packages/models/src/auth/Mfa/index.ts b/packages/models/src/auth/Mfa/index.ts new file mode 100644 index 00000000..a8b13866 --- /dev/null +++ b/packages/models/src/auth/Mfa/index.ts @@ -0,0 +1 @@ +export * from "./Mfa.js"; diff --git a/packages/models/src/auth/Mfa/types.ts b/packages/models/src/auth/Mfa/types.ts new file mode 100644 index 00000000..e45118f2 --- /dev/null +++ b/packages/models/src/auth/Mfa/types.ts @@ -0,0 +1,20 @@ +import { MittwaldAPIV2 } from "@mittwald/api-client"; + +export type MfaStatusData = + MittwaldAPIV2.Operations.UserGetMfaStatus.ResponseData; + +export type RecoverMfaRequestData = Omit< + MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Parameters.RequestBody, + "multiFactorCode" +> & { + recoveryCode: string; +}; + +export type ResetMfaRecoveryResponseData = + MittwaldAPIV2.Operations.UserResetRecoverycodes.ResponseData; + +export type ConfirmMfaResponseData = + MittwaldAPIV2.Operations.UserConfirmMfa.ResponseData; + +export type AuthenticateMfaRequestData = + MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Parameters.RequestBody; diff --git a/packages/models/src/auth/RecoveryCodes/RecoveryCodes.ts b/packages/models/src/auth/RecoveryCodes/RecoveryCodes.ts new file mode 100644 index 00000000..b8e70323 --- /dev/null +++ b/packages/models/src/auth/RecoveryCodes/RecoveryCodes.ts @@ -0,0 +1,11 @@ +import { DataModel } from "../../base/index.js"; +import { RecoveryCodesData } from "./types.js"; + +export class RecoveryCodes extends DataModel { + public readonly codes: string[]; + + public constructor(data: RecoveryCodesData) { + super(data); + this.codes = data.codes; + } +} diff --git a/packages/models/src/auth/RecoveryCodes/index.ts b/packages/models/src/auth/RecoveryCodes/index.ts new file mode 100644 index 00000000..81103f11 --- /dev/null +++ b/packages/models/src/auth/RecoveryCodes/index.ts @@ -0,0 +1 @@ +export * from "./RecoveryCodes.js"; diff --git a/packages/models/src/auth/RecoveryCodes/types.ts b/packages/models/src/auth/RecoveryCodes/types.ts new file mode 100644 index 00000000..1167ce57 --- /dev/null +++ b/packages/models/src/auth/RecoveryCodes/types.ts @@ -0,0 +1,3 @@ +export interface RecoveryCodesData { + codes: string[]; +} diff --git a/packages/models/src/auth/Session/Session.ts b/packages/models/src/auth/Session/Session.ts new file mode 100644 index 00000000..e32ea705 --- /dev/null +++ b/packages/models/src/auth/Session/Session.ts @@ -0,0 +1,20 @@ +import { DataModel } from "../../base/index.js"; +import { SessionData } from "./types.js"; +import { DateTime } from "luxon"; + +export class Session extends DataModel { + public readonly expirationDate: DateTime; + public readonly token: string; + public readonly refreshToken: string; + + public constructor(data: SessionData) { + super(data); + this.expirationDate = DateTime.fromISO(data.expires); + this.token = data.token; + this.refreshToken = data.refreshToken; + } + + public isExpired() { + return this.expirationDate > DateTime.now(); + } +} diff --git a/packages/models/src/auth/Session/index.ts b/packages/models/src/auth/Session/index.ts new file mode 100644 index 00000000..29e4cf71 --- /dev/null +++ b/packages/models/src/auth/Session/index.ts @@ -0,0 +1 @@ +export * from "./Session.js"; diff --git a/packages/models/src/auth/Session/types.ts b/packages/models/src/auth/Session/types.ts new file mode 100644 index 00000000..bb8c0396 --- /dev/null +++ b/packages/models/src/auth/Session/types.ts @@ -0,0 +1,5 @@ +export interface SessionData { + expires: string; + token: string; + refreshToken: string; +} diff --git a/packages/models/src/config/behaviors/api.ts b/packages/models/src/config/behaviors/api.ts index ffdf4aee..4dc65dc2 100644 --- a/packages/models/src/config/behaviors/api.ts +++ b/packages/models/src/config/behaviors/api.ts @@ -10,6 +10,7 @@ import { apiArticleBehaviors } from "../../article/Article/behaviors/index.js"; import { apiContractBehaviors } from "../../contract/Contract/behaviors/index.js"; import { apiContractItemBehaviors } from "../../contract/ContractItem/behaviors/index.js"; import { apiUserBehaviors } from "../../user/User/behaviors/index.js"; +import { apiMfaBehaviors } from "../../auth/Mfa/behaviors/index.js"; class ApiSetupState { private _client: MittwaldAPIV2Client | undefined; @@ -31,6 +32,7 @@ class ApiSetupState { config.behaviors.ingress = apiIngressBehaviors(client); config.behaviors.appInstallation = apiAppInstallationBehaviors(client); config.behaviors.user = apiUserBehaviors(client); + config.behaviors.mfa = apiMfaBehaviors(client); config.behaviors.contract = apiContractBehaviors(client); config.behaviors.contractItem = apiContractItemBehaviors(client); } diff --git a/packages/models/src/config/config.ts b/packages/models/src/config/config.ts index 10cbeeb2..484edc61 100644 --- a/packages/models/src/config/config.ts +++ b/packages/models/src/config/config.ts @@ -7,6 +7,7 @@ import { AppInstallationBehaviors } from "../app/AppInstallation/behaviors/index import { UserBehaviors } from "../user/User/behaviors/index.js"; import { ContractItemBehaviors } from "../contract/ContractItem/behaviors/index.js"; import { ArticleBehaviors } from "../article/Article/behaviors/index.js"; +import { MfaBehaviors } from "../auth/Mfa/behaviors/index.js"; interface Config { defaultPaginationLimit: number; @@ -20,6 +21,7 @@ interface Config { ingress: IngressBehaviors; appInstallation: AppInstallationBehaviors; user: UserBehaviors; + mfa: MfaBehaviors; }; } @@ -35,5 +37,6 @@ export const config: Config = { ingress: undefined as unknown as IngressBehaviors, appInstallation: undefined as unknown as AppInstallationBehaviors, user: undefined as unknown as UserBehaviors, + mfa: undefined as unknown as MfaBehaviors, }, }; diff --git a/packages/models/src/user/User/User.ts b/packages/models/src/user/User/User.ts index e28b66db..4bfd99a8 100644 --- a/packages/models/src/user/User/User.ts +++ b/packages/models/src/user/User/User.ts @@ -1,18 +1,10 @@ -import { - type AsyncResourceVariant, - provideReact, -} from "../../lib/provideReact.js"; import { config } from "../../config/config.js"; import { classes } from "polytype"; import { - UserAuthenticateMfaRequestData, - UserAuthenticateMfaResponseData, UserAuthenticateRequestData, - UserAuthenticateResponseData, UserConfirmPasswordResetRequestData, UserData, UserDeleteRequestData, - UserMfaStatusData, UserRegisterRequestData, UserRequestAvatarUploadResponseData, UserResendVerificationEmailRequestData, @@ -25,6 +17,9 @@ import { } from "./types.js"; import assertObjectFound from "../../base/assertObjectFound.js"; import { DataModel, ReferenceModel } from "../../base/index.js"; +import { AsyncResourceVariant, provideReact } from "../../react.js"; +import { Session } from "../../auth/Session/index.js"; +import { PendingMfaAuthentication } from "../../auth/Mfa/PendingMfaAuthentication.js"; export class User extends ReferenceModel { public static ofId(id: string): User { @@ -55,12 +50,12 @@ export class User extends ReferenceModel { public getDetailed = provideReact( () => User.get(this.id), [this.id], - ) as AsyncResourceVariant; + ) as AsyncResourceVariant<() => Promise>; public findDetailed = provideReact( () => User.find(this.id), [this.id], - ) as AsyncResourceVariant; + ) as AsyncResourceVariant<() => Promise>; public getPasswordUpdatedAt = provideReact( async (): Promise<{ passwordUpdatedAt: string }> => { @@ -68,10 +63,6 @@ export class User extends ReferenceModel { }, ); - public getMfaStatus = provideReact(async (): Promise => { - return await config.behaviors.user.getMfaStatus(); - }); - public async updatePersonalInformation( data: UserUpdatePersonalInformationRequestData, ): Promise { @@ -126,8 +117,12 @@ export class User extends ReferenceModel { public static async authenticate( data: UserAuthenticateRequestData, - ): Promise { - return await config.behaviors.user.authenticate(data); + ): Promise { + const result = await config.behaviors.user.authenticate(data); + if ("token" in result) { + return new Session(result); + } + return new PendingMfaAuthentication(data); } public static async register( @@ -151,28 +146,6 @@ export class User extends ReferenceModel { public static async delete(data: UserDeleteRequestData): Promise { await config.behaviors.user.delete(data); } - - public async authenticateMfa( - data: UserAuthenticateMfaRequestData, - ): Promise { - return await config.behaviors.user.authenticateMfa(data); - } - - public async confirmMfa( - multiFactorCode: string, - ): Promise<{ recoveryCodesList: string[] }> { - return await config.behaviors.user.confirmMfa(multiFactorCode); - } - - public async disableMfa(multiFactorCode: string): Promise { - await config.behaviors.user.disableMfa(multiFactorCode); - } - - public async resetRecoveryCodes( - multiFactorCode: string, - ): Promise<{ recoveryCodesList: string[] }> { - return await config.behaviors.user.resetRecoveryCodes(multiFactorCode); - } } class UserCommon extends classes(DataModel, User) { diff --git a/packages/models/src/user/User/behaviors/api.ts b/packages/models/src/user/User/behaviors/api.ts index 6a4b7da8..a1295315 100644 --- a/packages/models/src/user/User/behaviors/api.ts +++ b/packages/models/src/user/User/behaviors/api.ts @@ -5,7 +5,6 @@ import { } from "@mittwald/api-client"; import { UserBehaviors } from "./types.js"; import { - UserAuthenticateMfaRequestData, UserAuthenticateRequestData, UserConfirmPasswordResetRequestData, UserDeleteRequestData, @@ -34,14 +33,6 @@ export const apiUserBehaviors = ( return response.data; }, - getMfaStatus: async () => { - const response = await client.user.getMfaStatus({}); - - assertStatus(response, 200); - - return response.data; - }, - updatePersonalInformation: async (id, data) => { const response = await client.user.updatePersonalInformation({ userId: id, @@ -159,40 +150,4 @@ export const apiUserBehaviors = ( assertOneOfStatus(response, [200, 202]); }, - - authenticateMfa: async (data: UserAuthenticateMfaRequestData) => { - const response = await client.user.authenticateMfa({ data }); - - assertStatus(response, 200); - - return response.data; - }, - - confirmMfa: async (multiFactorCode: string) => { - const response = await client.user.confirmMfa({ - data: { multiFactorCode }, - }); - - assertStatus(response, 200); - - return response.data; - }, - - disableMfa: async (multiFactorCode: string) => { - const response = await client.user.disableMfa({ - data: { multiFactorCode }, - }); - - assertStatus(response, 204); - }, - - resetRecoveryCodes: async (multiFactorCode: string) => { - const response = await client.user.resetRecoverycodes({ - data: { multiFactorCode }, - }); - - assertStatus(response, 200); - - return response.data; - }, }); diff --git a/packages/models/src/user/User/behaviors/types.ts b/packages/models/src/user/User/behaviors/types.ts index ee584375..43f585d5 100644 --- a/packages/models/src/user/User/behaviors/types.ts +++ b/packages/models/src/user/User/behaviors/types.ts @@ -1,12 +1,9 @@ import { - UserAuthenticateMfaRequestData, - UserAuthenticateMfaResponseData, UserAuthenticateRequestData, UserAuthenticateResponseData, UserConfirmPasswordResetRequestData, UserData, UserDeleteRequestData, - UserMfaStatusData, UserRegisterRequestData, UserRequestAvatarUploadResponseData, UserResendVerificationEmailRequestData, @@ -23,8 +20,6 @@ export interface UserBehaviors { getPasswordUpdatedAt: () => Promise<{ passwordUpdatedAt: string }>; - getMfaStatus: () => Promise; - updatePersonalInformation: ( id: string, data: UserUpdatePersonalInformationRequestData, @@ -57,6 +52,7 @@ export interface UserBehaviors { authenticate: ( data: UserAuthenticateRequestData, ) => Promise; + logout: () => Promise; register: (data: UserRegisterRequestData) => Promise<{ id: string }>; @@ -68,15 +64,4 @@ export interface UserBehaviors { ) => Promise; delete: (data: UserDeleteRequestData) => Promise; - - authenticateMfa: ( - data: UserAuthenticateMfaRequestData, - ) => Promise; - confirmMfa: ( - multiFactorCode: string, - ) => Promise<{ recoveryCodesList: string[] }>; - disableMfa: (multiFactorCode: string) => Promise; - resetRecoveryCodes: ( - multiFactorCode: string, - ) => Promise<{ recoveryCodesList: string[] }>; } diff --git a/packages/models/src/user/User/types.ts b/packages/models/src/user/User/types.ts index daf0b272..10ca6a7d 100644 --- a/packages/models/src/user/User/types.ts +++ b/packages/models/src/user/User/types.ts @@ -2,9 +2,6 @@ import { MittwaldAPIV2 } from "@mittwald/api-client"; export type UserData = MittwaldAPIV2.Operations.UserGetUser.ResponseData; -export type UserMfaStatusData = - MittwaldAPIV2.Operations.UserGetMfaStatus.ResponseData; - export type UserUpdatePersonalInformationRequestData = MittwaldAPIV2.Paths.V2UsersSelfPersonalInformation.Put.Parameters.RequestBody; @@ -27,7 +24,9 @@ export type UserConfirmPasswordResetRequestData = MittwaldAPIV2.Paths.V2UsersSelfCredentialsPasswordConfirmReset.Post.Parameters.RequestBody; export type UserAuthenticateRequestData = - MittwaldAPIV2.Paths.V2Authenticate.Post.Parameters.RequestBody; + MittwaldAPIV2.Paths.V2Authenticate.Post.Parameters.RequestBody & { + multiFactorCode?: string; + }; export type UserAuthenticateResponseData = | MittwaldAPIV2.Paths.V2Authenticate.Post.Responses.$200.Content.ApplicationJson @@ -44,9 +43,3 @@ export type UserResendVerificationEmailRequestData = export type UserDeleteRequestData = MittwaldAPIV2.Paths.V2UsersSelf.Delete.Parameters.RequestBody; - -export type UserAuthenticateMfaRequestData = - MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Parameters.RequestBody; - -export type UserAuthenticateMfaResponseData = - MittwaldAPIV2.Paths.V2AuthenticateMfa.Post.Responses.$200.Content.ApplicationJson; diff --git a/yarn.lock b/yarn.lock index 7b828bdd..bead5c43 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1538,6 +1538,7 @@ __metadata: "@testing-library/react": "npm:^16.0.1" "@types/dinero.js": "npm:^1" "@types/jest": "npm:^29.5.12" + "@types/luxon": "npm:^3" "@types/react": "npm:^18.3.3" "@types/react-dom": "npm:^18" "@typescript-eslint/eslint-plugin": "npm:^7.18.0" @@ -1551,6 +1552,7 @@ __metadata: eslint-plugin-prettier: "npm:^5.2.1" jest: "npm:^29.7.0" jest-environment-jsdom: "npm:^29.7.0" + luxon: "npm:^3.5.0" object-code: "npm:^1.3.3" polytype: "npm:^0.17.0" prettier: "npm:^3.3.3" @@ -2850,7 +2852,7 @@ __metadata: languageName: node linkType: hard -"@types/luxon@npm:3.4.2": +"@types/luxon@npm:3.4.2, @types/luxon@npm:^3": version: 3.4.2 resolution: "@types/luxon@npm:3.4.2" checksum: 10/fd89566e3026559f2bc4ddcc1e70a2c16161905ed50be9473ec0cfbbbe919165041408c4f6e06c4bcf095445535052e2c099087c76b1b38e368127e618fc968d @@ -8128,7 +8130,7 @@ __metadata: languageName: node linkType: hard -"luxon@npm:~3.5.0": +"luxon@npm:^3.5.0, luxon@npm:~3.5.0": version: 3.5.0 resolution: "luxon@npm:3.5.0" checksum: 10/48f86e6c1c96815139f8559456a3354a276ba79bcef0ae0d4f2172f7652f3ba2be2237b0e103b8ea0b79b47715354ac9fac04eb1db3485dcc72d5110491dd47f