Skip to content

Commit

Permalink
Merge pull request #56 from OpenConext/add-gesture
Browse files Browse the repository at this point in the history
Add button to start webauthn flow
  • Loading branch information
pablothedude authored Jul 8, 2021
2 parents d27f944 + 171d5b1 commit fc5df04
Show file tree
Hide file tree
Showing 19 changed files with 1,246 additions and 1,105 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
/config/packages/parameters.yml
/build/
/var/*
/app/files/*
!/var/cache
/var/cache/*
!var/cache/.gitkeep
Expand Down
634 changes: 354 additions & 280 deletions composer.lock

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions public/typescript/model/application.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export function errorWithMailTo(error: ErrorInformation): error is ErrorWithMail
export type ErrorInformation = ErrorWithMail | ErrorWithoutMail;

export interface ApplicationState {
started: boolean;
requestInformation: RequestInformation;
errorInfo: ErrorInformation | null;
message: TranslationString;
Expand Down
13 changes: 13 additions & 0 deletions public/typescript/reducer/__test__/appReducer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ describe('appReducer', () => {
};
beforeEach(() => {
initialState = {
started: false,
errorInfo: null,
message: 'initial',
requestInformation,
Expand All @@ -30,13 +31,15 @@ describe('appReducer', () => {
shouldNotAlterState(state, action);
shouldNothaveAnError(state, action);
shouldSetMessageTo(state, action, 'status.authentication_initial');
shouldBeStarted(state, action);
});

describe('ApplicationEvent.REQUEST_USER_FOR_ATTESTATION', () => {
const action = { value: null, timestamp: 'now', type: ApplicationEvent.REQUEST_USER_FOR_ATTESTATION };
shouldNotAlterState(state, action);
shouldNothaveAnError(state, action);
shouldSetMessageTo(state, action, 'status.registration_initial');
shouldBeStarted(state, action);
});

describe('ApplicationEvent.PUBLIC_KEY_CREDENTIALS_SERIALIZED', () => {
Expand All @@ -48,6 +51,7 @@ describe('appReducer', () => {
const action = { value, timestamp: 'now', type: ApplicationEvent.PUBLIC_KEY_CREDENTIALS_SERIALIZED };
shouldNotAlterState(state, action);
shouldNothaveAnError(state, action);
shouldBeStarted(state, action);
it('Should decode clientDataJSON', () => {
const result = appReducer(initialState!, action);
expect(result.clientDataJSON).toEqual('123');
Expand All @@ -59,13 +63,15 @@ describe('appReducer', () => {
shouldNotAlterState(state, action);
shouldNotDisplayRetryAndMailToButton(state, action);
shouldSetMessageTo(state, action, 'status.webauthn_not_supported');
shouldBeStarted(state, action);
});

describe('ApplicationEvent.ERROR', () => {
const action = { value: null, timestamp: 'now', type: ApplicationEvent.ERROR };
shouldNotAlterState(state, action);
shouldDisplayMailAndRetryButton(state, action);
shouldSetMessageTo(state, action, 'status.general_error');
shouldBeStarted(state, action);
});
});

Expand Down Expand Up @@ -105,3 +111,10 @@ function shouldSetMessageTo(initialState: () => ApplicationState, action: Applic
expect(result.message).toEqual(message);
});
}

function shouldBeStarted(initialState: () => ApplicationState, action: ApplicationAction) {
it('Should be started', () => {
const result = appReducer(initialState(), action);
expect(result.started).toBeTruthy();
});
}
5 changes: 5 additions & 0 deletions public/typescript/reducer/appReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export const appReducer: AppReducer = (state, { value, type, timestamp }) => {
switch (type) {
case ApplicationEvent.NOT_SUPPORTED:
return {
started: true,
requestInformation,
message: 'status.webauthn_not_supported',
errorInfo: {
Expand All @@ -23,6 +24,7 @@ export const appReducer: AppReducer = (state, { value, type, timestamp }) => {

case ApplicationEvent.REQUEST_USER_FOR_ATTESTATION:
return {
started: true,
requestInformation,
message: 'status.registration_initial',
errorInfo: null,
Expand All @@ -32,11 +34,13 @@ export const appReducer: AppReducer = (state, { value, type, timestamp }) => {
const credentials: SerializedPublicKeyCredential = value as any;
return {
...state,
started: true,
clientDataJSON: decode(credentials.response.clientDataJSON).toString(),
};

case ApplicationEvent.REQUEST_USER_FOR_ASSERTION:
return {
started: true,
requestInformation,
message: 'status.authentication_initial',
errorInfo: null,
Expand All @@ -47,6 +51,7 @@ export const appReducer: AppReducer = (state, { value, type, timestamp }) => {
return serverResponseErrorReducer(state, timestamp, value.response);
}
return {
started: true,
requestInformation,
message: 'status.general_error',
errorInfo: {
Expand Down
4 changes: 4 additions & 0 deletions public/typescript/reducer/serverResponseErrorReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const serverResponseErrorReducer = (state: ApplicationState, timestamp: s
switch (status) {
case 'deviceNotSupported':
return {
started: true,
requestInformation,
message: 'status.authenticator_not_supported',
errorInfo: {
Expand All @@ -43,6 +44,7 @@ export const serverResponseErrorReducer = (state: ApplicationState, timestamp: s
case 'noRegistrationRequired':
case 'noAuthenticationRequired':
return {
started: true,
requestInformation,
message: 'status.no_active_request',
errorInfo: {
Expand All @@ -54,6 +56,7 @@ export const serverResponseErrorReducer = (state: ApplicationState, timestamp: s
};
case 'missingAttestationStatement':
return {
started: true,
requestInformation,
message: 'missing_attestation_statement',
errorInfo: {
Expand All @@ -66,6 +69,7 @@ export const serverResponseErrorReducer = (state: ApplicationState, timestamp: s
case 'invalid':
case 'error':
return {
started: true,
requestInformation,
message: 'status.general_error',
errorInfo: {
Expand Down
10 changes: 7 additions & 3 deletions public/typescript/ui/component/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ import { ErrorTable } from './ErrorTable';

export interface AppProps {
t: (key: string) => string; requestInformation: RequestInformation;
started: boolean;
startMessage: TranslationString;
errorInfo: ErrorInformation | null;
message: TranslationString;
onClick: () => void;
onStart: () => void;
}

export const App: FC<AppProps> = ({ t, message, requestInformation, errorInfo, onClick }) => {
export const App: FC<AppProps> = ({ t, started, startMessage, message, requestInformation, errorInfo, onClick, onStart }) => {
return (
<div>
<p>
{t(message)}
</p>
{errorInfo && <ErrorTable t={t} errorInfo={errorInfo} clientInfo={requestInformation} />}
{errorInfo && errorInfo.showRetry && <button onClick={onClick}>{t('retry')}</button>}
{started && errorInfo && <ErrorTable t={t} errorInfo={errorInfo} clientInfo={requestInformation} />}
{started && errorInfo && errorInfo.showRetry && <button className="btn btn-primary" onClick={onClick}>{t('retry')}</button>}
{!started && <button className="btn btn-primary" onClick={onStart}>{t(startMessage)}</button>}
</div>
);
};
6 changes: 3 additions & 3 deletions public/typescript/ui/component/AuthenticationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export interface AuthenticationContainerProps {

export const AuthenticationContainer: FC<AuthenticationContainerProps> = ({ t, requestInformation, publicKeyOptions, responseUrl }) => {
const [state, dispatch] = useAppReducer(requestInformation, 'status.authentication_initial');
const { message, errorInfo } = state;
const { message, errorInfo, started } = state;
const [click, clicked] = useClickable();
const verify = useVerifyPublicKeyCredentials(responseUrl);
useAuthenticationEffect(dispatch, publicKeyOptions, verify, clicked);
return <App message={message} errorInfo={errorInfo} requestInformation={requestInformation} t={t} onClick={click} />;
const onStart = useAuthenticationEffect(dispatch, publicKeyOptions, verify, clicked);
return <App started={started} startMessage="authentication.start_button" message={message} errorInfo={errorInfo} requestInformation={requestInformation} t={t} onClick={click} onStart={onStart} />;
};
6 changes: 3 additions & 3 deletions public/typescript/ui/component/RegistrationContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export interface RegistrationContainerProps {

export const RegistrationContainer: FC<RegistrationContainerProps> = ({ t, responseUrl, publicKeyOptions, requestInformation }) => {
const [state, dispatch] = useAppReducer(requestInformation, 'status.registration_initial');
const { message, errorInfo } = state;
const { message, errorInfo, started } = state;
const [click, clicked] = useClickable();
const verify = useVerifyPublicKeyCredentials(responseUrl);
useRegistrationEffect(dispatch, publicKeyOptions, verify, clicked);
return <App message={message} errorInfo={errorInfo} requestInformation={requestInformation} t={t} onClick={click} />;
const onStart = useRegistrationEffect(dispatch, publicKeyOptions, verify, clicked);
return <App started={started} startMessage="registration.start_button" message={message} errorInfo={errorInfo} requestInformation={requestInformation} t={t} onClick={click} onStart={onStart} />;
};
1 change: 1 addition & 0 deletions public/typescript/ui/hook/useAppReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { loggingAppReducerDecorator } from '../../reducer/loggingAppReducerDecor
export const useAppReducer = (requestInformation: RequestInformation, initialMessage: string) => useReducer(
loggingAppReducerDecorator(appReducer),
useState({
started: false,
message: initialMessage,
errorInfo: null,
requestInformation,
Expand Down
4 changes: 2 additions & 2 deletions public/typescript/ui/hook/useAuthenticationEffect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useCallback } from 'react';
import { Observable } from 'rxjs';
import { reloadPage } from '../../function';
import { PublicKeyResponseValidator } from '../../function/http';
Expand All @@ -7,7 +7,7 @@ import { authenticationObservable } from '../../observable';
import { retryWith } from '../../operator';

export const useAuthenticationEffect = (dispatch: (action: ApplicationAction) => void, publicKeyOptions: SerializedPublicKeyCredentialRequestOptions, send: PublicKeyResponseValidator, whenClicked: Observable<unknown>) =>
useEffect(
useCallback(
() => {
const time = () => (new Date()).toISOString();
const subscription = authenticationObservable(
Expand Down
4 changes: 2 additions & 2 deletions public/typescript/ui/hook/useRegistrationEffect.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useCallback } from 'react';
import { Observable } from 'rxjs';
import { reloadPage } from '../../function';
import { PublicKeyResponseValidator } from '../../function/http';
Expand All @@ -7,7 +7,7 @@ import { registrationObservable } from '../../observable';
import { retryWith } from '../../operator';

export const useRegistrationEffect = (dispatch: (action: ApplicationAction) => void, publicKeyOptions: SerializedPublicKeyCredentialCreationOptions, send: PublicKeyResponseValidator, whenClicked: Observable<unknown>) =>
useEffect(
useCallback(
() => {
const time = () => (new Date()).toISOString();
const subscription = registrationObservable(
Expand Down
3 changes: 3 additions & 0 deletions symfony.lock
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,9 @@
"symfony/polyfill-php80": {
"version": "v1.18.1"
},
"symfony/polyfill-php81": {
"version": "v1.23.0"
},
"symfony/profiler-pack": {
"version": "v1.0.4"
},
Expand Down
4 changes: 2 additions & 2 deletions templates/default/authentication.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
{% block body %}
<h2>{{ 'authentication.sub_title'|trans }}</h2>

<div id="root"> </div>
<div id="root">
</div>

{% include 'default/variables.twig' %}
{% block javascripts %}
Expand All @@ -15,4 +16,3 @@
{% endblock %}

{% endblock %}

3 changes: 1 addition & 2 deletions templates/default/registration.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{% block body %}
<h2>{{ 'registration.sub_title'|trans }}</h2>

<div id="root"> </div>
<div id="root"></div>

{% include 'default/variables.twig' %}
{% block javascripts %}
Expand All @@ -15,4 +15,3 @@
{% endblock %}

{% endblock %}

2 changes: 2 additions & 0 deletions templates/default/variables.twig
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
{% set translations = {
'authentication.start_button': 'authentication.start_button' | trans,
'registration.start_button': 'registration.start_button' | trans,
'registration.sub_title': 'registration.sub_title' | trans,
'status.registration_initial': 'status.registration_initial' | trans,
'status.missing_attestation_statement': 'status.missing_attestation_statement' | trans,
Expand Down
4 changes: 3 additions & 1 deletion translations/messages.en.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Registration

registration.sub_title: Registration
registration.start_button: Use your security key
status.registration_initial: Register your FIDO2 token.
status.missing_attestation_statement: Try again and grant access to view your FIDO2 token.

# Authentication

authentication.sub_title: Authentication
authentication.start_button: Use your security key
status.authentication_initial: Authenticate with your FIDO2 token.

# General status messages
Expand All @@ -20,7 +22,7 @@ status.authenticator_not_supported: This token is not supported, try with a diff

## Buttons

retry: Retry again
retry: Try again
cancel: Cancel
abort: Abort

Expand Down
2 changes: 2 additions & 0 deletions translations/messages.nl.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
# Registration

registration.sub_title: Registratie
registration.start_button: Gebruik je beveiligingssleutel
status.registration_initial: Registreer jouw FIDO2 token.
status.missing_attestation_statement: Probeer het opnieuw en verleen toegang om je FIDO2 token te bekijken.

# Authentication

authentication.sub_title: Authenticeren
authentication.start_button: Gebruik je beveiligingssleutel
status.authentication_initial: Authenticeer met jouw FIDO2 token.

# General status messages
Expand Down
Loading

0 comments on commit fc5df04

Please sign in to comment.