Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion web/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,10 @@
"client_setup_mobile_forgot": "If you forgot to install the mobile app, click one of the buttons bellow.",
"client_setup_mobile_google": "Google Play",
"client_setup_mobile_apple": "Apple Store",
"client_setup_footer_extra": "Once your Defguard client is configured, you can close this window."
"client_setup_footer_extra": "Once your Defguard client is configured, you can close this window.",
"openid_mfa_redirect_error_title": "Authentication Error",
"openid_mfa_redirect_error_message": "No token provided in the URL. Please ensure you have a valid token to proceed with OpenID authentication.",
"openid_mfa_redirect_error_missing_args": "Missing code or state in the callback's URL. The provider might not be configured correctly.",
"openid_mfa_complete_title": "Authentication Completed",
"openid_mfa_complete_subtitle": "You have been successfully authenticated. Please close this window and get back to the Defguard VPN Client"
}
63 changes: 63 additions & 0 deletions web/src/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as PasswordIndexRouteImport } from './routes/password/index'
import { Route as PasswordSentRouteImport } from './routes/password/sent'
import { Route as PasswordFinishRouteImport } from './routes/password/finish'
import { Route as OpenidErrorRouteImport } from './routes/openid/error'
import { Route as OpenidCallbackRouteImport } from './routes/openid/callback'
import { Route as OpenidMfaIndexRouteImport } from './routes/openid/mfa/index'
import { Route as OpenidMfaCallbackRouteImport } from './routes/openid/mfa/callback'

const TestRoute = TestRouteImport.update({
id: '/test',
Expand Down Expand Up @@ -71,11 +74,26 @@ const PasswordFinishRoute = PasswordFinishRouteImport.update({
path: '/password/finish',
getParentRoute: () => rootRouteImport,
} as any)
const OpenidErrorRoute = OpenidErrorRouteImport.update({
id: '/openid/error',
path: '/openid/error',
getParentRoute: () => rootRouteImport,
} as any)
const OpenidCallbackRoute = OpenidCallbackRouteImport.update({
id: '/openid/callback',
path: '/openid/callback',
getParentRoute: () => rootRouteImport,
} as any)
const OpenidMfaIndexRoute = OpenidMfaIndexRouteImport.update({
id: '/openid/mfa/',
path: '/openid/mfa/',
getParentRoute: () => rootRouteImport,
} as any)
const OpenidMfaCallbackRoute = OpenidMfaCallbackRouteImport.update({
id: '/openid/mfa/callback',
path: '/openid/mfa/callback',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
Expand All @@ -86,9 +104,12 @@ export interface FileRoutesByFullPath {
'/session-end': typeof SessionEndRoute
'/test': typeof TestRoute
'/openid/callback': typeof OpenidCallbackRoute
'/openid/error': typeof OpenidErrorRoute
'/password/finish': typeof PasswordFinishRoute
'/password/sent': typeof PasswordSentRoute
'/password': typeof PasswordIndexRoute
'/openid/mfa/callback': typeof OpenidMfaCallbackRoute
'/openid/mfa': typeof OpenidMfaIndexRoute
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
Expand All @@ -99,9 +120,12 @@ export interface FileRoutesByTo {
'/session-end': typeof SessionEndRoute
'/test': typeof TestRoute
'/openid/callback': typeof OpenidCallbackRoute
'/openid/error': typeof OpenidErrorRoute
'/password/finish': typeof PasswordFinishRoute
'/password/sent': typeof PasswordSentRoute
'/password': typeof PasswordIndexRoute
'/openid/mfa/callback': typeof OpenidMfaCallbackRoute
'/openid/mfa': typeof OpenidMfaIndexRoute
}
export interface FileRoutesById {
__root__: typeof rootRouteImport
Expand All @@ -113,9 +137,12 @@ export interface FileRoutesById {
'/session-end': typeof SessionEndRoute
'/test': typeof TestRoute
'/openid/callback': typeof OpenidCallbackRoute
'/openid/error': typeof OpenidErrorRoute
'/password/finish': typeof PasswordFinishRoute
'/password/sent': typeof PasswordSentRoute
'/password/': typeof PasswordIndexRoute
'/openid/mfa/callback': typeof OpenidMfaCallbackRoute
'/openid/mfa/': typeof OpenidMfaIndexRoute
}
export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
Expand All @@ -128,9 +155,12 @@ export interface FileRouteTypes {
| '/session-end'
| '/test'
| '/openid/callback'
| '/openid/error'
| '/password/finish'
| '/password/sent'
| '/password'
| '/openid/mfa/callback'
| '/openid/mfa'
fileRoutesByTo: FileRoutesByTo
to:
| '/'
Expand All @@ -141,9 +171,12 @@ export interface FileRouteTypes {
| '/session-end'
| '/test'
| '/openid/callback'
| '/openid/error'
| '/password/finish'
| '/password/sent'
| '/password'
| '/openid/mfa/callback'
| '/openid/mfa'
id:
| '__root__'
| '/'
Expand All @@ -154,9 +187,12 @@ export interface FileRouteTypes {
| '/session-end'
| '/test'
| '/openid/callback'
| '/openid/error'
| '/password/finish'
| '/password/sent'
| '/password/'
| '/openid/mfa/callback'
| '/openid/mfa/'
fileRoutesById: FileRoutesById
}
export interface RootRouteChildren {
Expand All @@ -168,9 +204,12 @@ export interface RootRouteChildren {
SessionEndRoute: typeof SessionEndRoute
TestRoute: typeof TestRoute
OpenidCallbackRoute: typeof OpenidCallbackRoute
OpenidErrorRoute: typeof OpenidErrorRoute
PasswordFinishRoute: typeof PasswordFinishRoute
PasswordSentRoute: typeof PasswordSentRoute
PasswordIndexRoute: typeof PasswordIndexRoute
OpenidMfaCallbackRoute: typeof OpenidMfaCallbackRoute
OpenidMfaIndexRoute: typeof OpenidMfaIndexRoute
}

declare module '@tanstack/react-router' {
Expand Down Expand Up @@ -245,13 +284,34 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PasswordFinishRouteImport
parentRoute: typeof rootRouteImport
}
'/openid/error': {
id: '/openid/error'
path: '/openid/error'
fullPath: '/openid/error'
preLoaderRoute: typeof OpenidErrorRouteImport
parentRoute: typeof rootRouteImport
}
'/openid/callback': {
id: '/openid/callback'
path: '/openid/callback'
fullPath: '/openid/callback'
preLoaderRoute: typeof OpenidCallbackRouteImport
parentRoute: typeof rootRouteImport
}
'/openid/mfa/': {
id: '/openid/mfa/'
path: '/openid/mfa'
fullPath: '/openid/mfa'
preLoaderRoute: typeof OpenidMfaIndexRouteImport
parentRoute: typeof rootRouteImport
}
'/openid/mfa/callback': {
id: '/openid/mfa/callback'
path: '/openid/mfa/callback'
fullPath: '/openid/mfa/callback'
preLoaderRoute: typeof OpenidMfaCallbackRouteImport
parentRoute: typeof rootRouteImport
}
}
}

Expand All @@ -264,9 +324,12 @@ const rootRouteChildren: RootRouteChildren = {
SessionEndRoute: SessionEndRoute,
TestRoute: TestRoute,
OpenidCallbackRoute: OpenidCallbackRoute,
OpenidErrorRoute: OpenidErrorRoute,
PasswordFinishRoute: PasswordFinishRoute,
PasswordSentRoute: PasswordSentRoute,
PasswordIndexRoute: PasswordIndexRoute,
OpenidMfaCallbackRoute: OpenidMfaCallbackRoute,
OpenidMfaIndexRoute: OpenidMfaIndexRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
Expand Down
37 changes: 25 additions & 12 deletions web/src/routes/openid/callback.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,33 @@ const schema = z.object({
export const Route = createFileRoute('/openid/callback')({
component: RouteComponent,
validateSearch: schema,
onError: () => {
throw redirect({ to: '/enrollment-start', replace: true });
},
loaderDeps: ({ search }) => ({ search }),
beforeLoad: async ({ search }) => {
const openIdResponse = await api.openId.enrollmentCallback.callbackFn({
data: {
code: search.code,
state: search.state,
type: 'enrollment',
},
});
const enrollmentStartResponse = await api.enrollment.start.callbackFn({
data: {
token: openIdResponse.data.token,
},
});
const openIdResponse = await api.openId.enrollmentCallback
.callbackFn({
data: {
code: search.code,
state: search.state,
type: 'enrollment',
},
})
.catch((e) => {
console.error(e);
throw redirect({ to: '/enrollment-start', replace: true });
});
const enrollmentStartResponse = await api.enrollment.start
.callbackFn({
data: {
token: openIdResponse.data.token,
},
})
.catch((e) => {
console.error(e);
throw redirect({ to: '/enrollment-start', replace: true });
});
useEnrollmentStore.setState({
enrollmentData: enrollmentStartResponse.data,
token: openIdResponse.data.token,
Expand Down
22 changes: 22 additions & 0 deletions web/src/routes/openid/error.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { createFileRoute } from '@tanstack/react-router';
import { m } from '../../paraglide/messages';
import { PageProcessEnd } from '../../shared/components/PageProcessEnd/PageProcessEnd';
import { useOpenidStore } from '../../shared/hooks/useOpenIdStore';

export const Route = createFileRoute('/openid/error')({
component: RouteComponent,
});

function RouteComponent() {
const openIdError = useOpenidStore(
(s) => s.error ?? m.openid_mfa_redirect_error_message(),
);

return (
<PageProcessEnd
title={m.openid_mfa_redirect_error_title()}
subtitle={openIdError}
icon="disabled"
/>
);
}
44 changes: 44 additions & 0 deletions web/src/routes/openid/mfa/callback.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { createFileRoute, redirect } from '@tanstack/react-router';
import z from 'zod';
import { m } from '../../../paraglide/messages';
import { api } from '../../../shared/api/api';
import { PageProcessEnd } from '../../../shared/components/PageProcessEnd/PageProcessEnd';
import { useOpenidStore } from '../../../shared/hooks/useOpenIdStore';

const searchSchema = z.object({
code: z.string().trim().min(1),
state: z.string().trim().min(1),
});

export const Route = createFileRoute('/openid/mfa/callback')({
validateSearch: searchSchema,
onError: () => {
useOpenidStore.setState({ error: m.openid_mfa_redirect_error_missing_args() });
throw redirect({ to: '/openid/error', replace: true });
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ deps }) => {
try {
await api.openId.mfaCallback.callbackFn({
data: {
code: deps.search.code,
state: deps.search.state,
type: 'mfa',
},
});
} catch (e) {
console.error(e);
}
},
component: RouteComponent,
});

function RouteComponent() {
return (
<PageProcessEnd
title={m.openid_mfa_complete_title()}
subtitle={m.openid_mfa_complete_subtitle()}
icon="check-circle"
/>
);
}
58 changes: 58 additions & 0 deletions web/src/routes/openid/mfa/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { createFileRoute, redirect, useLoaderData } from '@tanstack/react-router';
import { useEffect } from 'react';
import z from 'zod';
import { api } from '../../../shared/api/api';
import { isPresent } from '../../../shared/defguard-ui/utils/isPresent';

const searchSchema = z.object({
token: z.string().trim().min(1),
});

export const Route = createFileRoute('/openid/mfa/')({
component: RouteComponent,
validateSearch: searchSchema,
onError: () => {
throw redirect({
to: '/openid/error',
replace: true,
});
},
loaderDeps: ({ search }) => ({ search }),
loader: async ({ deps }) => {
const response = await api.openId.authInfo
.callbackFn({
data: {
type: 'mfa',
state: deps.search.token,
},
})
.catch((e) => {
console.error(e);
throw redirect({
to: '/openid/error',
replace: true,
});
});
if (!isPresent(response.data.url)) {
console.error('Missing URL in server response.');
throw redirect({
to: '/openid/error',
replace: true,
});
}
return response.data.url as string;
},
});

function RouteComponent() {
const loaderData = useLoaderData({
from: '/openid/mfa/',
});

useEffect(() => {
if (loaderData) {
window.location.href = loaderData;
}
}, [loaderData]);
return null;
}
Loading