From 330781b13f390e204e06664152580b082d77946f Mon Sep 17 00:00:00 2001 From: Baozier Date: Wed, 30 Oct 2024 22:18:30 -0400 Subject: [PATCH] Support change password policy (#186) --- .../__tests__/normal/identity-main.test.tsx | 8 + .../__tests__/normal/identity-mfa.test.tsx | 8 + .../__tests__/normal/identity-policy.test.tsx | 176 ++++++++++++++++++ .../__tests__/normal/identity-social.test.tsx | 2 + server/src/configs/locale.ts | 27 +++ server/src/configs/route.ts | 1 + server/src/dtos/identity.ts | 11 ++ server/src/dtos/oauth.ts | 11 ++ server/src/handlers/identity/index.tsx | 1 + server/src/handlers/identity/main.tsx | 21 ++- server/src/handlers/identity/policy.tsx | 55 ++++++ server/src/handlers/oauth.ts | 1 + server/src/routes/identity.tsx | 13 ++ server/src/services/user.ts | 27 +++ server/src/utils/identity.ts | 16 +- server/src/views/ChangePassword.tsx | 109 +++++++++++ server/src/views/index.tsx | 2 + server/src/views/scripts/request.ts | 2 + server/src/views/scripts/response.ts | 22 +-- 19 files changed, 494 insertions(+), 19 deletions(-) create mode 100644 server/src/__tests__/normal/identity-policy.test.tsx create mode 100644 server/src/handlers/identity/policy.tsx create mode 100644 server/src/views/ChangePassword.tsx diff --git a/server/src/__tests__/normal/identity-main.test.tsx b/server/src/__tests__/normal/identity-main.test.tsx index 9abdc0cd..dd61cf27 100644 --- a/server/src/__tests__/normal/identity-main.test.tsx +++ b/server/src/__tests__/normal/identity-main.test.tsx @@ -340,6 +340,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) const { code } = json as { code: string } const codeStore = JSON.parse(await mockedKV.get(`AC-${code}`) ?? '') @@ -690,6 +691,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) const appRecord = await getApp(db) const { code } = json as { code: string } @@ -752,6 +754,7 @@ describe( requireOtpSetup: true, requireOtpMfa: true, requireSmsMfa: false, + requireChangePassword: false, }) global.process.env.OTP_MFA_IS_REQUIRED = false as unknown as string }, @@ -774,6 +777,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) global.process.env.EMAIL_MFA_IS_REQUIRED = false as unknown as string }, @@ -796,6 +800,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: true, + requireChangePassword: false, }) global.process.env.SMS_MFA_IS_REQUIRED = false as unknown as string }, @@ -818,6 +823,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) global.process.env.ENFORCE_ONE_MFA_ENROLLMENT = ['email', 'otp'] as unknown as string }, @@ -840,6 +846,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) global.process.env.ENABLE_USER_APP_CONSENT = true as unknown as string }, @@ -1006,6 +1013,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) const consent = db.prepare('SELECT * from user_app_consent WHERE "userId" = 1 AND "appId" = 1').get() expect(consent).toBeTruthy() diff --git a/server/src/__tests__/normal/identity-mfa.test.tsx b/server/src/__tests__/normal/identity-mfa.test.tsx index 3f82d75e..695e88ac 100644 --- a/server/src/__tests__/normal/identity-mfa.test.tsx +++ b/server/src/__tests__/normal/identity-mfa.test.tsx @@ -231,6 +231,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) const user = await db.prepare('SELECT * from "user" WHERE id = 1').get() as userModel.Raw @@ -322,6 +323,7 @@ describe( requireOtpSetup: true, requireOtpMfa: true, requireSmsMfa: false, + requireChangePassword: false, }) const user = await db.prepare('SELECT * from "user" WHERE id = 1').get() as userModel.Raw @@ -544,6 +546,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.OtpMfaCode}-${json.code}`)).toBe('1') }, @@ -1516,6 +1519,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.SmsMfaCode}-${json.code}`)).toBe('1') @@ -1896,6 +1900,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.EmailMfaCode}-${json.code}`)).toBe('1') }, @@ -1994,6 +1999,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.OtpMfaCode}-${json.code}`)).toBe('1') }, @@ -2055,6 +2061,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.SmsMfaCode}-${json.code}`)).toBe('1') }, @@ -2235,6 +2242,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) expect(await mockedKV.get(`${adapterConfig.BaseKVKey.EmailMfaCode}-${json.code}`)).toBe('1') }, diff --git a/server/src/__tests__/normal/identity-policy.test.tsx b/server/src/__tests__/normal/identity-policy.test.tsx new file mode 100644 index 00000000..763bce82 --- /dev/null +++ b/server/src/__tests__/normal/identity-policy.test.tsx @@ -0,0 +1,176 @@ +import { + afterEach, beforeEach, describe, expect, test, +} from 'vitest' +import { Database } from 'better-sqlite3' +import { JSDOM } from 'jsdom' +import app from 'index' +import { + migrate, mock, + mockedKV, +} from 'tests/mock' +import { routeConfig } from 'configs' +import { + prepareFollowUpBody, prepareFollowUpParams, + insertUsers, postSignInRequest, getApp, +} from 'tests/identity' + +let db: Database + +beforeEach(async () => { + db = await migrate() +}) + +afterEach(async () => { + await db.close() + await mockedKV.empty() +}) + +describe( + 'get /change-password', + () => { + test( + 'should show change password page', + async () => { + await insertUsers( + db, + false, + ) + const params = await prepareFollowUpParams(db) + + const res = await app.request( + `${routeConfig.IdentityRoute.ChangePassword}${params}`, + {}, + mock(db), + ) + + const html = await res.text() + const dom = new JSDOM(html) + const document = dom.window.document + expect(document.getElementsByName('password').length).toBe(1) + expect(document.getElementsByName('confirmPassword').length).toBe(1) + expect(document.getElementsByTagName('form').length).toBe(1) + expect(document.getElementsByTagName('select').length).toBe(1) + }, + ) + + test( + 'should redirect if use wrong auth code', + async () => { + await insertUsers( + db, + false, + ) + await prepareFollowUpParams(db) + + const res = await app.request( + `${routeConfig.IdentityRoute.ChangePassword}?locale=en&code=abc`, + {}, + mock(db), + ) + expect(res.status).toBe(302) + expect(res.headers.get('Location')).toBe(`${routeConfig.IdentityRoute.AuthCodeExpired}?locale=en`) + }, + ) + + test( + 'could disable locale selector', + async () => { + global.process.env.ENABLE_LOCALE_SELECTOR = false as unknown as string + await insertUsers( + db, + false, + ) + const params = await prepareFollowUpParams(db) + + const res = await app.request( + `${routeConfig.IdentityRoute.ChangePassword}${params}`, + {}, + mock(db), + ) + + const html = await res.text() + const dom = new JSDOM(html) + const document = dom.window.document + expect(document.getElementsByTagName('select').length).toBe(0) + global.process.env.ENABLE_LOCALE_SELECTOR = true as unknown as string + }, + ) + }, +) + +describe( + 'post /authorize-consent', + () => { + test( + 'should change password', + async () => { + await insertUsers( + db, + false, + ) + const body = await prepareFollowUpBody(db) + + const res = await app.request( + routeConfig.IdentityRoute.ChangePassword, + { + method: 'POST', + body: JSON.stringify({ + ...body, + password: 'Password2!', + }), + }, + mock(db), + ) + const json = await res.json() + expect(json).toStrictEqual({ success: true }) + + const appRecord = await getApp(db) + const reLoginRes = await postSignInRequest( + db, + appRecord, + { password: 'Password2!' }, + ) + const loginResJson = await reLoginRes.json() as { code: string } + expect(loginResJson).toStrictEqual({ + code: expect.any(String), + redirectUri: 'http://localhost:3000/en/dashboard', + state: '123', + scopes: ['profile', 'openid', 'offline_access'], + requireConsent: true, + requireMfaEnroll: true, + requireEmailMfa: false, + requireSmsMfa: false, + requireOtpSetup: false, + requireOtpMfa: false, + requireChangePassword: false, + }) + }, + ) + + test( + 'should redirect if use wrong auth code', + async () => { + await insertUsers( + db, + false, + ) + await prepareFollowUpBody(db) + + const res = await app.request( + routeConfig.IdentityRoute.ChangePassword, + { + method: 'POST', + body: JSON.stringify({ + locale: 'en', + code: 'abc', + password: 'Password2!', + }), + }, + mock(db), + ) + expect(res.status).toBe(302) + expect(res.headers.get('Location')).toBe(`${routeConfig.IdentityRoute.AuthCodeExpired}?locale=en`) + }, + ) + }, +) diff --git a/server/src/__tests__/normal/identity-social.test.tsx b/server/src/__tests__/normal/identity-social.test.tsx index 08bff10b..7eda6974 100644 --- a/server/src/__tests__/normal/identity-social.test.tsx +++ b/server/src/__tests__/normal/identity-social.test.tsx @@ -92,6 +92,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) } @@ -281,6 +282,7 @@ describe( requireOtpSetup: false, requireOtpMfa: false, requireSmsMfa: false, + requireChangePassword: false, }) } diff --git a/server/src/configs/locale.ts b/server/src/configs/locale.ts index f2dcbb8a..3a85f110 100644 --- a/server/src/configs/locale.ts +++ b/server/src/configs/locale.ts @@ -391,6 +391,33 @@ export const authorizeReset = Object.freeze({ }, }) +export const changePassword = Object.freeze({ + title: { + en: 'Update your password', + fr: 'Mettez à jour votre mot de passe', + }, + success: { + en: 'Password updated!', + fr: 'Mot de passe mis à jour !', + }, + newPassword: { + en: 'New Password', + fr: 'Nouveau mot de passe', + }, + confirmNewPassword: { + en: 'Confirm New Password', + fr: 'Confirmez le nouveau mot de passe', + }, + confirm: { + en: 'Confirm', + fr: 'Confirmer', + }, + redirect: { + en: 'Redirect back', + fr: 'Rediriger en arrière', + }, +}) + export const verifyEmail = Object.freeze({ title: { en: 'Verify your email', diff --git a/server/src/configs/route.ts b/server/src/configs/route.ts index b50aa2ad..f4faa137 100644 --- a/server/src/configs/route.ts +++ b/server/src/configs/route.ts @@ -37,4 +37,5 @@ export enum IdentityRoute { ResendResetCode = `${InternalRoute.Identity}/resend-reset-code`, VerifyEmail = `${InternalRoute.Identity}/verify-email`, Logout = `${InternalRoute.Identity}/logout`, + ChangePassword = `${InternalRoute.Identity}/change-password`, } diff --git a/server/src/dtos/identity.ts b/server/src/dtos/identity.ts index 2a3ec5a2..1a2b8cb8 100644 --- a/server/src/dtos/identity.ts +++ b/server/src/dtos/identity.ts @@ -145,6 +145,17 @@ export class PostAuthorizeEnrollReqDto extends GetAuthorizeFollowUpReqDto { } } +export class PostChangePasswordReqDto extends GetAuthorizeFollowUpReqDto { + @IsString() + @IsNotEmpty() + password: string + + constructor (dto: PostChangePasswordReqDto) { + super(dto) + this.password = dto.password + } +} + export class PostLogoutReqDto { @IsString() @IsNotEmpty() diff --git a/server/src/dtos/oauth.ts b/server/src/dtos/oauth.ts index e733f20b..5526ba6d 100644 --- a/server/src/dtos/oauth.ts +++ b/server/src/dtos/oauth.ts @@ -1,5 +1,6 @@ import { ArrayMinSize, IsEnum, IsString, IsNotEmpty, + IsOptional, } from 'class-validator' import { typeConfig } from 'configs' @@ -18,6 +19,11 @@ export enum TokenGrantType { ClientCredentials = 'client_credentials', } +export enum Policy { + SignInOrSignUp = 'sign_in_or_sign_up', + ChangePassword = 'change_password', +} + const parseScopes = (scopes: string[]) => scopes.map((s) => s.trim().toLowerCase()) export class GetAuthorizeReqDto { @@ -50,6 +56,10 @@ export class GetAuthorizeReqDto { @IsString() locale: typeConfig.Locale + @IsEnum(Policy) + @IsOptional() + policy?: string | undefined + constructor (dto: GetAuthorizeReqDto) { this.clientId = dto.clientId this.redirectUri = dto.redirectUri.toLowerCase() @@ -59,6 +69,7 @@ export class GetAuthorizeReqDto { this.codeChallengeMethod = dto.codeChallengeMethod.toLowerCase() this.scopes = parseScopes(dto.scopes) this.locale = dto.locale + this.policy = dto.policy } } diff --git a/server/src/handlers/identity/index.tsx b/server/src/handlers/identity/index.tsx index 86b5d292..3852d359 100644 --- a/server/src/handlers/identity/index.tsx +++ b/server/src/handlers/identity/index.tsx @@ -2,3 +2,4 @@ export * from 'handlers/identity/main' export * from 'handlers/identity/other' export * from 'handlers/identity/social' export * from 'handlers/identity/mfa' +export * from 'handlers/identity/policy' diff --git a/server/src/handlers/identity/main.tsx b/server/src/handlers/identity/main.tsx index f759060d..697f3f7c 100644 --- a/server/src/handlers/identity/main.tsx +++ b/server/src/handlers/identity/main.tsx @@ -21,25 +21,34 @@ import { } from 'views' import { userModel } from 'models' import { oauthHandler } from 'handlers' +import { Policy } from 'dtos/oauth' export const getAuthorizePassword = async (c: Context) => { const queryDto = await oauthHandler.parseGetAuthorizeDto(c) const { COMPANY_LOGO_URL: logoUrl, - ENABLE_SIGN_UP: enableSignUp, - ENABLE_PASSWORD_RESET: enablePasswordReset, - ENABLE_PASSWORD_SIGN_IN: enablePasswordSignIn, + ENABLE_SIGN_UP: allowSignUp, + ENABLE_PASSWORD_RESET: allowPasswordReset, + ENABLE_PASSWORD_SIGN_IN: allowPasswordSignIn, SUPPORTED_LOCALES: locales, ENABLE_LOCALE_SELECTOR: enableLocaleSelector, - GOOGLE_AUTH_CLIENT_ID: googleClientId, - FACEBOOK_AUTH_CLIENT_ID: facebookClientId, + GOOGLE_AUTH_CLIENT_ID: googleAuthId, + FACEBOOK_AUTH_CLIENT_ID: facebookAuthId, FACEBOOK_AUTH_CLIENT_SECRET: facebookClientSecret, - GITHUB_AUTH_CLIENT_ID: githubClientId, + GITHUB_AUTH_CLIENT_ID: githubAuthId, GITHUB_AUTH_CLIENT_SECRET: githubClientSecret, GITHUB_AUTH_APP_NAME: githubAppName, } = env(c) + const isChangePassword = queryDto.policy === Policy.ChangePassword + const enablePasswordReset = isChangePassword ? false : allowPasswordReset + const enableSignUp = isChangePassword ? false : allowSignUp + const enablePasswordSignIn = isChangePassword ? true : allowPasswordSignIn + const googleClientId = isChangePassword ? '' : googleAuthId + const facebookClientId = isChangePassword ? '' : facebookAuthId + const githubClientId = isChangePassword ? '' : githubAuthId + const queryString = requestUtil.getQueryString(c) return c.html() => { + const queryDto = await identityDto.parseGetAuthorizeFollowUpReq(c) + + const authInfo = await kvService.getAuthCodeBody( + c.env.KV, + queryDto.code, + ) + if (!authInfo) return c.redirect(`${routeConfig.IdentityRoute.AuthCodeExpired}?locale=${queryDto.locale}`) + + const { + COMPANY_LOGO_URL: logoUrl, + SUPPORTED_LOCALES: locales, + ENABLE_LOCALE_SELECTOR: enableLocaleSelector, + } = env(c) + + return c.html() +} + +export const postChangePassword = async (c: Context) => { + const reqBody = await c.req.json() + + const bodyDto = new identityDto.PostChangePasswordReqDto(reqBody) + await validateUtil.dto(bodyDto) + + const authInfo = await kvService.getAuthCodeBody( + c.env.KV, + bodyDto.code, + ) + if (!authInfo) return c.redirect(`${routeConfig.IdentityRoute.AuthCodeExpired}?locale=${bodyDto.locale}`) + + await userService.changeUserPassword( + c, + authInfo.user, + bodyDto, + ) + + return c.json({ success: true }) +} diff --git a/server/src/handlers/oauth.ts b/server/src/handlers/oauth.ts index c545361b..257a4027 100644 --- a/server/src/handlers/oauth.ts +++ b/server/src/handlers/oauth.ts @@ -31,6 +31,7 @@ export const parseGetAuthorizeDto = async (c: Context): Prom c, c.req.query('locale'), ), + policy: c.req.query('policy') ?? undefined, }) await validateUtil.dto(queryDto) diff --git a/server/src/routes/identity.tsx b/server/src/routes/identity.tsx index adab3b25..8958a139 100644 --- a/server/src/routes/identity.tsx +++ b/server/src/routes/identity.tsx @@ -190,3 +190,16 @@ identityRoutes.get( routeConfig.IdentityRoute.AuthCodeExpired, identityHandler.getAuthCodeExpired, ) + +identityRoutes.get( + routeConfig.IdentityRoute.ChangePassword, + configMiddleware.enablePasswordReset, + identityHandler.getChangePassword, +) + +identityRoutes.post( + routeConfig.IdentityRoute.ChangePassword, + setupMiddleware.validOrigin, + configMiddleware.enablePasswordReset, + identityHandler.postChangePassword, +) diff --git a/server/src/services/user.ts b/server/src/services/user.ts index 2f635181..55a3807d 100644 --- a/server/src/services/user.ts +++ b/server/src/services/user.ts @@ -412,6 +412,33 @@ export const resetUserPassword = async ( return true } +export const changeUserPassword = async ( + c: Context, + user: userModel.Record, + bodyDto: identityDto.PostChangePasswordReqDto, +): Promise => { + if (!user.password) { + throw new errorConfig.NotFound(localeConfig.Error.NoUser) + } + + const isSame = cryptoUtil.bcryptCompare( + bodyDto.password, + user.password, + ) + if (isSame) { + throw new errorConfig.Forbidden(localeConfig.Error.RequireDifferentPassword) + } + + const password = await cryptoUtil.bcryptText(bodyDto.password) + + await userModel.update( + c.env.DB, + user.id, + { password }, + ) + return true +} + export const enrollUserMfa = async ( c: Context, authId: string, diff --git a/server/src/utils/identity.ts b/server/src/utils/identity.ts index c5a2a1ae..a9a42f44 100644 --- a/server/src/utils/identity.ts +++ b/server/src/utils/identity.ts @@ -6,6 +6,7 @@ import { } from 'services' import { AuthCodeBody } from 'configs/type' import { userModel } from 'models' +import { Policy } from 'dtos/oauth' export enum AuthorizeStep { Account = 0, @@ -16,6 +17,7 @@ export enum AuthorizeStep { OtpMfa = 3, SmsMfa = 4, EmailMfa = 5, + ChangePassword = 6, } export const processPostAuthorize = async ( @@ -31,12 +33,14 @@ export const processPostAuthorize = async ( ) const isSocialLogin = !!authCodeBody.user.socialAccountId + const isChangePasswordPolicy = authCodeBody.request.policy === Policy.ChangePassword const { EMAIL_MFA_IS_REQUIRED: enableEmailMfa, OTP_MFA_IS_REQUIRED: enableOtpMfa, SMS_MFA_IS_REQUIRED: enableSmsMfa, ENFORCE_ONE_MFA_ENROLLMENT: enforceMfa, + ENABLE_PASSWORD_RESET: enablePasswordReset, } = env(c) const requireMfaEnroll = @@ -64,7 +68,16 @@ export const processPostAuthorize = async ( !isSocialLogin && (enableEmailMfa || authCodeBody.user.mfaTypes.includes(userModel.MfaType.Email)) - if (!requireConsent && !requireMfaEnroll && !requireOtpMfa && !requireEmailMfa && !requireSmsMfa) { + const requireChangePassword = + step < 6 && + !isSocialLogin && + enablePasswordReset && + isChangePasswordPolicy + + if ( + !isChangePasswordPolicy && !requireConsent && !requireMfaEnroll && + !requireOtpMfa && !requireEmailMfa && !requireSmsMfa + ) { sessionService.setAuthInfoSession( c, authCodeBody.appId, @@ -85,5 +98,6 @@ export const processPostAuthorize = async ( requireSmsMfa, requireOtpSetup, requireOtpMfa, + requireChangePassword, } } diff --git a/server/src/views/ChangePassword.tsx b/server/src/views/ChangePassword.tsx new file mode 100644 index 00000000..349d5d31 --- /dev/null +++ b/server/src/views/ChangePassword.tsx @@ -0,0 +1,109 @@ +import { html } from 'hono/html' +import SubmitError from './components/SubmitError' +import { + localeConfig, + routeConfig, + typeConfig, +} from 'configs' +import Layout from 'views/components/Layout' +import { + resetErrorScript, responseScript, validateScript, +} from 'views/scripts' +import Title from 'views/components/Title' +import Field from 'views/components/Field' +import { identityDto } from 'dtos' +import SubmitButton from 'views/components/SubmitButton' + +const ChangePassword = ({ + logoUrl, queryDto, locales, redirectUri, +}: { + logoUrl: string; + queryDto: identityDto.GetAuthorizeFollowUpReqDto; + locales: typeConfig.Locale[]; + redirectUri: string; +}) => { + return ( + + +
+ + <SubmitError /> + <form + onsubmit='return handleSubmit(event)' + > + <section class='flex-col gap-4'> + <Field + label={localeConfig.changePassword.newPassword[queryDto.locale]} + type='password' + required + name='password' + /> + <Field + label={localeConfig.changePassword.confirmNewPassword[queryDto.locale]} + type='password' + required + name='confirmPassword' + /> + <SubmitButton + title={localeConfig.changePassword.confirm[queryDto.locale]} + /> + </section> + </form> + </section> + {html` + <script> + ${resetErrorScript.resetPasswordError()} + ${resetErrorScript.resetConfirmPasswordError()} + function handleSubmit (e) { + e.preventDefault(); + ${validateScript.password(queryDto.locale)} + ${validateScript.confirmPassword(queryDto.locale)} + fetch('${routeConfig.IdentityRoute.ChangePassword}', { + method: 'POST', + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + code: "${queryDto.code}", + locale: "${queryDto.locale}", + password: document.getElementById('form-password').value, + }) + }) + .then((response) => { + ${responseScript.parseRes()} + }) + .then((data) => { + document.getElementById('submit-form').classList.add('hidden'); + document.getElementById('success-message').classList.remove('hidden'); + }) + .catch((error) => { + ${responseScript.handleSubmitError(queryDto.locale)} + }); + return false; + } + </script> + `} + </Layout> + ) +} + +export default ChangePassword diff --git a/server/src/views/index.tsx b/server/src/views/index.tsx index c55ebdcd..55afb7cb 100644 --- a/server/src/views/index.tsx +++ b/server/src/views/index.tsx @@ -8,6 +8,7 @@ import AuthorizeSmsMfaView from 'views/AuthorizeSmsMfa' import AuthorizeMfaEnrollView from 'views/AuthorizeMfaEnroll' import VerifyEmailView from 'views/VerifyEmail' import AuthCodeExpired from 'views/AuthCodeExpired' +import ChangePassword from 'views/ChangePassword' export { AuthorizeAccountView, @@ -20,4 +21,5 @@ export { AuthorizeSmsMfaView, AuthorizeMfaEnrollView, AuthCodeExpired, + ChangePassword, } diff --git a/server/src/views/scripts/request.ts b/server/src/views/scripts/request.ts index 906140a5..5c7f75aa 100644 --- a/server/src/views/scripts/request.ts +++ b/server/src/views/scripts/request.ts @@ -1,11 +1,13 @@ import { html } from 'hono/html' import { oauthDto } from 'dtos' +import { Policy } from 'dtos/oauth' export const parseAuthorizeBaseValues = (queryDto: oauthDto.GetAuthorizeReqDto) => html` clientId: "${queryDto.clientId}", redirectUri: "${queryDto.redirectUri}", responseType: "${queryDto.responseType}", state: "${queryDto.state}", + policy: "${queryDto.policy ?? Policy.SignInOrSignUp}", codeChallenge: "${queryDto.codeChallenge}", codeChallengeMethod: "${queryDto.codeChallengeMethod}", locale: "${queryDto.locale}", diff --git a/server/src/views/scripts/response.ts b/server/src/views/scripts/response.ts index 862d4727..f782b09d 100644 --- a/server/src/views/scripts/response.ts +++ b/server/src/views/scripts/response.ts @@ -15,29 +15,27 @@ export const parseRes = () => html` export const handleAuthorizeFormRedirect = (locale: typeConfig.Locale) => html` var queryString = "?state=" + data.state + "&code=" + data.code + "&locale=" + "${locale}"; + var innerQueryString = queryString += "&redirect_uri=" + data.redirectUri; if (data.requireConsent) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeConsent}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeConsent}" + innerQueryString window.location.href = url; } else if (data.requireMfaEnroll) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeMfaEnroll}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeMfaEnroll}" + innerQueryString window.location.href = url; } else if (data.requireOtpSetup) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeOtpSetup}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeOtpSetup}" + innerQueryString window.location.href = url; } else if (data.requireOtpMfa) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeOtpMfa}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeOtpMfa}" + innerQueryString window.location.href = url; } else if (data.requireSmsMfa) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeSmsMfa}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeSmsMfa}" + innerQueryString window.location.href = url; } else if (data.requireEmailMfa) { - queryString += "&redirect_uri=" + data.redirectUri; - var url = "${routeConfig.IdentityRoute.AuthorizeEmailMfa}" + queryString + var url = "${routeConfig.IdentityRoute.AuthorizeEmailMfa}" + innerQueryString + window.location.href = url; + } else if (data.requireChangePassword) { + var url = "${routeConfig.IdentityRoute.ChangePassword}" + innerQueryString window.location.href = url; } else { var url = data.redirectUri + queryString;