Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
15826ca
IS-11243 Fix search input left padding by removing overly broad input…
May 13, 2026
35b21bd
IS-11327 LWA: WebAuthn ceremony errors as HAAPI step AppErrors
aleixsuau May 20, 2026
cb48e2b
IS-11327 Rename metadata.messages to metadata.viewData; extract getHa…
aleixsuau May 20, 2026
65476cc
IS-11327: reduce doc useless verbosity
aleixsuau May 20, 2026
16cc0cb
IS-11327: refactor webauthn error handling
aleixsuau May 20, 2026
58574ef
IS-11327 Move shared ClientOperationResult to operations/typings.ts
aleixsuau May 21, 2026
713e473
IS-10863 SSP: add dial code select in phone number dialog
luisgoncalves May 22, 2026
b7fe3cc
IS-10863 Align country code and phone number inputs side by side
May 25, 2026
c7d1864
IS-10863 Add country code mocks for previewer
May 25, 2026
a0b9804
IS-10863 Add country code label translations for SSP phone number dialog
May 25, 2026
ccec75d
IS-10863 SSP: do not sort countries when rendering; add placeholder o…
luisgoncalves May 25, 2026
584ad93
Merge pull request #198 from curityio/feature/dev/IS-10863-ssp-phone-…
luisgoncalves May 25, 2026
2818521
IS-11368 LWA: forward HaapiBaseFormField.required to rendered field i…
aleixsuau May 25, 2026
57743ad
IS-11368 Default createMockFormAction fields to required: true.
aleixsuau May 25, 2026
bb475b4
Potential fix for pull request finding
urre May 26, 2026
3dc4341
Merge pull request #165 from curityio/fix/dev/IS-11243-search-input-l…
urre May 26, 2026
b7b8fad
IS-11368 Default required attr to true when field.required is omitted.
aleixsuau May 26, 2026
20d5905
IS-11368: remove default field required
aleixsuau May 26, 2026
b462945
IS-5161 Merge branch 'dev' of github.com:curityio/ui-kit into integra…
May 26, 2026
01600a7
IS-11318 LWA: rework page symbols data in bootstrap configuration
luisgoncalves May 26, 2026
1d13d9b
Merge branch 'integration/IS-5161/login-web-app' into feature/IS-1136…
aleixsuau May 27, 2026
90f61bd
Merge pull request #205 from curityio/feature/IS-5161/IS-11318-update…
luisgoncalves May 27, 2026
a4c4f2a
Merge pull request #200 from curityio/feature/IS-11368-required-attr-…
aleixsuau May 27, 2026
3a4d2d5
IS-11318 LWA: fix propagation of bootstrap configuration in the dev s…
luisgoncalves May 27, 2026
78ff304
Merge pull request #204 from curityio/fix/integration/IS-5161/login-w…
aleixsuau May 27, 2026
c1dec04
IS-11327 Rethrow non-DOMException from getWebAuthnErrorType
aleixsuau May 28, 2026
7dbbaf6
IS-11327 Type WebAuthn runner mocks against their real signatures
aleixsuau May 28, 2026
f21525f
IS-11327: remove useless comment
aleixsuau May 28, 2026
66bea62
IS-11327: return failed error for null credentials
aleixsuau May 28, 2026
a1cdd99
IS-11327: remove viewData error messages
aleixsuau May 28, 2026
c582a9d
IS-5161 Wrap HaapiStepperStepUI in App.tsx with HaapiStepperErrorNoti…
aleixsuau May 26, 2026
69660e4
Merge pull request #203 from curityio/feature/IS-5161/wrap-haapi-step…
aleixsuau May 28, 2026
1432273
IS-11346: render page symbols for HAAPI steps
aleixsuau May 28, 2026
da52027
Merge pull request #206 from curityio/feature/IS-5161/IS-11318-dev-lo…
luisgoncalves May 28, 2026
a038fc1
IS-11346: prettier
aleixsuau May 29, 2026
b2dd1d0
IS-11346: refcator to use exec
aleixsuau May 29, 2026
f442182
IS-11346: add AppConfigContext to previewer
aleixsuau May 29, 2026
3db12cb
IS-11327 LWA: WebAuthn ceremony errors as HAAPI step AppErrors
aleixsuau May 20, 2026
78d2938
IS-11327 Rename metadata.messages to metadata.viewData; extract getHa…
aleixsuau May 20, 2026
e1211c7
IS-11327: reduce doc useless verbosity
aleixsuau May 20, 2026
bd01831
IS-11327: refactor webauthn error handling
aleixsuau May 20, 2026
836c0d7
IS-11327 Move shared ClientOperationResult to operations/typings.ts
aleixsuau May 21, 2026
15a4654
IS-11327 Rethrow non-DOMException from getWebAuthnErrorType
aleixsuau May 28, 2026
7b76888
IS-11327 Type WebAuthn runner mocks against their real signatures
aleixsuau May 28, 2026
c65a98f
IS-11327: remove useless comment
aleixsuau May 28, 2026
e7eacaf
IS-11327: return failed error for null credentials
aleixsuau May 28, 2026
1784e8c
IS-11327: remove viewData error messages
aleixsuau May 28, 2026
e6b4e00
Merge branch 'feature/IS-11327/webauthn-error-handling' of github.com…
aleixsuau May 29, 2026
ebd5201
IS-11327 Move getHaapiStepperError to operations/helpers.ts
aleixsuau May 29, 2026
f11acb1
Merge pull request #194 from curityio/feature/IS-11327/webauthn-error…
aleixsuau May 29, 2026
683f709
IS-11346: render page symbols for HAAPI steps
aleixsuau May 28, 2026
f0f560e
IS-11346: prettier
aleixsuau May 29, 2026
7a7f23c
IS-11346: refcator to use exec
aleixsuau May 29, 2026
eca8653
IS-11346: add AppConfigContext to previewer
aleixsuau May 29, 2026
9a8a0ad
Merge branch 'feature/IS-5161/IS-11346-display-page-symbols' of githu…
aleixsuau May 29, 2026
87f95aa
IS-5161 IS-11346 wrap page symbol in figure and use CSS variables for…
May 29, 2026
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
1 change: 0 additions & 1 deletion src/common/css/lib/src/base/base-forms.css
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,6 @@ input[data-autocompleted] {
details {
border: 1px solid var(--color-grey-light);
border-radius: var(--form-field-border-radius);
margin-bottom: var(--space-2);
padding: var(--space-2) var(--space-2) 0;
}

Expand Down
13 changes: 2 additions & 11 deletions src/identity-server/styles/css/login-base.css
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,8 @@
}
}

[type='submit']:where(:not(.button-tiny, .button-small, .button-large)) {
font-size: calc(var(--button-font-size) * var(--y-medium));
min-height: 44px;
padding-block-start: calc(var(--button-padding-y) * var(--y-medium));
padding-block-end: calc(var(--button-padding-y) * var(--y-medium));
padding-inline-start: calc(var(--button-padding-x) * var(--x-medium));
padding-inline-end: calc(var(--button-padding-x) * var(--x-medium));

&:where(:not([class*='-outline'])) {
color: white;
}
.login-well [type="submit"] {
margin-block-start: var(--space-2);
}

/* Forgot password links */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#*
* Copyright (C) 2026 Curity AB. All rights reserved.
*
* The contents of this file are the property of Curity AB.
* You may not copy or use this file, in either source code
* or executable form, except in compliance with terms
* set by Curity AB.
*
* For further information, please contact Curity AB.
*#

{
logo: {
path: '$!logo_path',
isInsideWell: #if ($logo_inside) true #else false #end,
},
#if ($show_symbol)
#macro(symbolPath $jsonKey $pathVariable)
#if($pathVariable)'$jsonKey': '$!pathVariable',#end
#end

pageSymbols: {
## Map of plugin implementation type to symbol path.
## Used as a fallback if an exact match for the page symbol is not found in 'views'.
plugins: {
#* Authenticators *#
#symbolPath("bankid" $page_symbol_authenticate_bankid)
#symbolPath("duo" $page_symbol_authenticate_pair_device)
#symbolPath("email" $page_symbol_authenticate_email)
#symbolPath("html-form" $page_symbol_authenticate_htmlform)
#symbolPath("openid-wallet" $page_symbol_openid_wallet)
#symbolPath("passkeys" $page_symbol_authenticate_passkeys)
#symbolPath("sms" $page_symbol_authenticate_sms)
#symbolPath("totp" $page_symbol_authenticate_totp)
#symbolPath("webauthn" $page_symbol_authenticate_webauthn)

#* Authentication ACtions *#
#symbolPath("opt-in-mfa" $page_symbol_authenticate_opt_in_mfa)
#symbolPath("require-active-account" $page_symbol_authenticate_htmlform)
#symbolPath("reset-password" $page_symbol_authenticate_htmlform)
#symbolPath("signup" $page_symbol_authenticate_htmlform)

#* Consentors *#
#symbolPath("bankid-signing-consentor" $page_symbol_authenticate_bankid)
},
## Map of view/template name to symbol path.
views: {
## Nothing to add for now
},
default: '$!page_symbol',
}
#end
}
33 changes: 1 addition & 32 deletions src/identity-server/templates/core/views/api-driven-ui/index.vm
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,7 @@
clientId: '$_apiDrivenClientInfo.clientId',
tokenEndpoint: '$_apiDrivenClientInfo.tokenEndpoint',
},
theme: {
logo: {
path: '$!logo_path',
isInsideWell: #if ($logo_inside) true #else false #end,
},
#if ($show_symbol)
pageSymbols: {
## Free form mapping of page symbols names to full paths.
#macro(pageSymbolPath $jsonKey $variable)
#if($variable)$jsonKey: '$!variable',#end
#end
paths: {
#pageSymbolPath("authenticate_desktop" $page_symbol_authenticate_desktop)
#pageSymbolPath("authenticate_mobile" $page_symbol_authenticate_mobile)
#pageSymbolPath("authenticate_email" $page_symbol_authenticate_email)
#pageSymbolPath("authenticate_card" $page_symbol_authenticate_card)
#pageSymbolPath("authenticate_sms" $page_symbol_authenticate_sms)
#pageSymbolPath("authenticate_pair_device" $page_symbol_authenticate_pair_device)
#pageSymbolPath("authenticate_htmlform" $page_symbol_authenticate_htmlform)
#pageSymbolPath("authenticate_totp" $page_symbol_authenticate_totp)
#pageSymbolPath("authenticate_bankid" $page_symbol_authenticate_bankid)
#pageSymbolPath("authenticate_webauthn" $page_symbol_authenticate_webauthn)
#pageSymbolPath("authenticate_opt_in_mfa" $page_symbol_authenticate_opt_in_mfa)
#pageSymbolPath("authenticate_passkeys" $page_symbol_authenticate_passkeys)
#pageSymbolPath("authenticate_openid_wallet" $page_symbol_openid_wallet)
#pageSymbolPath("error_generic" $page_symbol_error_generic)
},
## Added in case the UI needs to build paths for other page symbols.
basePath: '$!page_symbol_path',
},
#end
},
theme: #parse("fragments/api-driven-ui/theme")
};
</script>
<div id="root"></div>
Expand Down
2 changes: 1 addition & 1 deletion src/login-web-app/configure-idsvr-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,4 @@ API_DRIVEN_UI_DIR=$OVERRIDES_DIR/views/api-driven-ui

mkdir -p $API_DRIVEN_UI_DIR

cp ./loader-dev.vm.html $API_DRIVEN_UI_DIR/index.vm
cp ./loader-dev.vm $API_DRIVEN_UI_DIR/index.vm
8 changes: 1 addition & 7 deletions src/login-web-app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,7 @@
}

// Set the configuration object expected by the application
window.__CONFIG__ = {
initialUrl: arg('initialUrl'),
haapi: {
clientId: arg('haapi_clientId'),
tokenEndpoint: arg('haapi_tokenEndpoint'),
},
};
window.__CONFIG__ = JSON.parse(arg('configuration'));

// Change the current URL to match the original request to Identity Server, so that it looks like the
// application was loaded directly by the server's template
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,27 @@
</head>
<body>
<script>
const configuration = {
initialUrl: '$_apiDrivenInitialUrl',
haapi: {
clientId: '$_apiDrivenClientInfo.clientId',
tokenEndpoint: '$_apiDrivenClientInfo.tokenEndpoint',
},
theme: {
logo: {
path: '$!logo_path',
isInsideWell: #if ($logo_inside) true #else false #end,
},
#if ($show_symbol)
pageSymbols: {
default: '$!page_symbol',
}
#end
},
};
const query = new URLSearchParams({
originalUrl: window.location.href,
initialUrl: '$_apiDrivenInitialUrl',
haapi_clientId: '$_apiDrivenClientInfo.clientId',
haapi_tokenEndpoint: '$_apiDrivenClientInfo.tokenEndpoint',
configuration: JSON.stringify(configuration),
});

const redirect = new URL('/', window.location.href);
Expand Down
22 changes: 17 additions & 5 deletions src/login-web-app/previewer/Previewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ import { formatNextStepData } from '../src/haapi-stepper/feature/stepper/data-fo
import { HaapiStepperContext } from '../src/haapi-stepper/feature/stepper/HaapiStepperContext';
import { type HaapiStepperAPI } from '../src/haapi-stepper/feature/stepper/haapi-stepper.types';
import { HaapiErrorStep } from '../src/haapi-stepper/data-access';
import { AppConfigContext } from '../src/shared/feature/app-config/AppConfigContext';
import type { BootstrapConfiguration } from '../src/haapi-stepper/data-access/bootstrap-configuration';

const mockAppConfig: BootstrapConfiguration = {
initialUrl: '',
haapi: { clientId: '', tokenEndpoint: '' },
theme: {
logo: { path: '', isInsideWell: false },
},
};

enum Page {
START = 'start',
Expand Down Expand Up @@ -56,10 +66,12 @@ export function Previewer() {
};

return (
<HaapiStepperContext value={mockHaapiStepperValue}>
<Layout onNavigate={page => setCurrentPage(page as Page)} currentPage={currentPage}>
{renderPreview()}
</Layout>
</HaapiStepperContext>
<AppConfigContext value={mockAppConfig}>
<HaapiStepperContext value={mockHaapiStepperValue}>
<Layout onNavigate={page => setCurrentPage(page as Page)} currentPage={currentPage}>
{renderPreview()}
</Layout>
</HaapiStepperContext>
</AppConfigContext>
);
}
5 changes: 4 additions & 1 deletion src/login-web-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import { AppConfig } from './shared/feature/app-config/AppConfig';
import { ErrorBoundary } from './shared/feature/error-handling/ErrorBoundary';
import { HaapiStepperStepUI } from './haapi-stepper/feature/steps/HaapiStepperStepUI';
import { HaapiStepper } from './haapi-stepper/feature/stepper/HaapiStepper';
import { HaapiStepperErrorNotifier } from './haapi-stepper/feature/stepper/HaapiStepperErrorNotifier';

export function App() {
return (
<AppConfig>
<ErrorBoundary>
<HaapiStepper>
<Layout>
<HaapiStepperStepUI />
<HaapiStepperErrorNotifier>
<HaapiStepperStepUI />
</HaapiStepperErrorNotifier>
</Layout>
</HaapiStepper>
</ErrorBoundary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,23 @@ export interface BootstrapConfiguration {
path: string;
isInsideWell: boolean;
};
/**
* Optional per-page icon configuration. Only present when symbols are enabled in the server theme.
* Resolved against the current step's `metadata.viewName` (see `resolvePageSymbol`).
*/
pageSymbols?: PageSymbols;
};
}

export interface PageSymbols {
/** Map of full HAAPI viewName -> symbol path. Highest precedence. */
views?: Record<string, string>;
/** Map of plugin implementation type (e.g. `html-form`, `bankid`) -> symbol path. */
plugins?: Record<string, string>;
/** Fallback symbol path used when no per-view / per-plugin entry matches. */
default?: string;
}

// @ts-expect-error window.__CONFIG__ is not declared on the Window type
const _configuration = window.__CONFIG__ as BootstrapConfiguration | undefined;
if (!_configuration) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,30 +184,27 @@ export interface HaapiExternalBrowserArguments {
href: string;
}

/**
* Client operation WebAuthn registration action
*/
export interface HaapiWebAuthnRegistrationClientOperationAction extends HaapiClientOperationAction {
model: HaapiWebAuthnRegistrationClientOperationModel;
}
export type HaapiWebAuthnRegistrationClientOperationAction =
| HaapiWebAuthnPasskeysRegistrationAction
| HaapiWebAuthnAnyDeviceRegistrationAction;

export interface HaapiWebAuthnRegistrationClientOperationModel extends HaapiBaseClientOperationModel {
name: HAAPI_ACTION_CLIENT_OPERATIONS.WEBAUTHN_REGISTRATION;
arguments: HaapiWebAuthnRegistrationArgs;
continueActions: [HaapiFormAction];
}

export type HaapiWebAuthnPasskeysRegistrationAction = Omit<HaapiWebAuthnRegistrationClientOperationAction, 'model'> & {
export interface HaapiWebAuthnPasskeysRegistrationAction extends HaapiClientOperationAction {
model: Omit<HaapiWebAuthnRegistrationClientOperationModel, 'arguments'> & {
arguments: HaapiWebAuthnPasskeysArgs;
};
};
}

export type HaapiWebAuthnAnyDeviceRegistrationAction = Omit<HaapiWebAuthnRegistrationClientOperationAction, 'model'> & {
export interface HaapiWebAuthnAnyDeviceRegistrationAction extends HaapiClientOperationAction {
model: Omit<HaapiWebAuthnRegistrationClientOperationModel, 'arguments'> & {
arguments: HaapiWebAuthnAnyDeviceArgs;
};
};
}

/**
* Discriminated union of `webauthn-registration` action arguments.
Expand All @@ -223,10 +220,15 @@ export interface HaapiWebAuthnPasskeysArgs {
credentialCreationOptions: HaapiPublicKeyCredentialCreationOptions;
}

export interface HaapiWebAuthnAnyDeviceArgs {
platformCredentialCreationOptions?: HaapiPublicKeyCredentialCreationOptions;
crossPlatformCredentialCreationOptions?: HaapiPublicKeyCredentialCreationOptions;
}
export type HaapiWebAuthnAnyDeviceArgs =
| {
platformCredentialCreationOptions: HaapiPublicKeyCredentialCreationOptions;
crossPlatformCredentialCreationOptions?: HaapiPublicKeyCredentialCreationOptions;
}
| {
platformCredentialCreationOptions?: undefined;
crossPlatformCredentialCreationOptions: HaapiPublicKeyCredentialCreationOptions;
};

/**
* Continue-action payload key for the `webauthn-registration` operation. The value matches the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,9 @@ import {
HAAPI_ACTION_TYPES,
HaapiAction,
} from '../../../../data-access/types/haapi-action.types';
import { HaapiLink } from '../../../../data-access/types/haapi-step.types';
import { HaapiLink, HaapiStep } from '../../../../data-access/types/haapi-step.types';
import { RefObject } from 'react';
import { HaapiStepperAction, HaapiStepperLink } from '../../../stepper/haapi-stepper.types';
import { HaapiFetchFormAction } from '../../../../data-access/types/haapi-fetch.types';
import { isBankIdClientOperation, runBankIdAuthentication } from './bankid';
import { isExternalBrowserFlowClientOperation, runExternalBrowserFlow } from './external-browser-flow';
import {
Expand All @@ -26,51 +25,37 @@ import {
runWebAuthnAuthentication,
runWebAuthnRegistration,
} from './webauthn';
import { ClientOperationResult } from './typings';

export function isClientOperation(
action: HaapiAction | HaapiStepperAction | HaapiLink | HaapiStepperLink
): action is HaapiClientOperationAction {
return 'template' in action && action.template === HAAPI_ACTION_TYPES.CLIENT_OPERATION;
}

/**
* Performs a client operation, returning a continuation action and values if further action is required, or null if
* no further action is required or if the operation was aborted.
*/
export async function performClientOperation(
action: HaapiClientOperationAction,
pendingOperation: RefObject<AbortController | NodeJS.Timeout | null>
): Promise<HaapiFetchFormAction | null> {
pendingOperation: RefObject<AbortController | NodeJS.Timeout | null>,
currentStep: HaapiStep | null
): Promise<ClientOperationResult> {
const abortController = new AbortController();
pendingOperation.current = abortController;
const signal = abortController.signal;

try {
if (isExternalBrowserFlowClientOperation(action)) {
return await runExternalBrowserFlow(action, 2500, abortController.signal);
}

if (isWebAuthnRegistrationClientOperation(action)) {
return await runWebAuthnRegistration(action, abortController.signal);
}
if (isWebAuthnRegistrationClientOperation(action)) {
return runWebAuthnRegistration(action, signal, currentStep);
}

if (isWebAuthnAuthenticationClientOperation(action)) {
return await runWebAuthnAuthentication(action, abortController.signal);
}
if (isWebAuthnAuthenticationClientOperation(action)) {
return runWebAuthnAuthentication(action, signal, currentStep);
}

if (isBankIdClientOperation(action)) {
return await runBankIdAuthentication(action);
}
} catch (err) {
/**
* If the operation was aborted by the caller, convert to null - i.e. no further action - instead of error
* Note that the cancellation is triggered by code on this file and a 'reason' is not provided, so we can rely on
* the error being the default AbortError.
*/
if (abortController.signal.aborted && err instanceof DOMException && err.name === 'AbortError') {
return null;
}
if (isExternalBrowserFlowClientOperation(action)) {
return runExternalBrowserFlow(action, 2500, signal).then(clientOperationData => ({ clientOperationData }));
}

throw err;
if (isBankIdClientOperation(action)) {
return runBankIdAuthentication(action).then(clientOperationData => ({ clientOperationData }));
}

throw new Error(`Unsupported client operation: ${action.model.name}`);
Expand Down
Loading
Loading