From 47ad34b6a64245adb0760450df27978efbf1d43d Mon Sep 17 00:00:00 2001 From: Patrik Date: Mon, 4 Sep 2023 16:01:56 +0200 Subject: [PATCH] feat: support i18n (#284) --- package-lock.json | 21 ++- package.json | 5 +- src/index.ts | 18 ++- src/pkg/index.ts | 9 +- src/pkg/middleware.ts | 26 +++- src/pkg/ui.ts | 32 ---- src/routes/404.ts | 2 +- src/routes/500.ts | 2 +- src/routes/consent.ts | 6 +- src/routes/error.ts | 7 +- src/routes/health.ts | 2 +- src/routes/login.ts | 57 +++---- src/routes/recovery.ts | 22 +-- src/routes/registration.ts | 44 +++--- src/routes/sessions.ts | 4 +- src/routes/settings.ts | 144 ++---------------- src/routes/static.ts | 4 +- src/routes/verification.ts | 22 +-- src/routes/welcome.ts | 2 +- views/layouts/auth.hbs | 5 +- views/layouts/settings.hbs | 3 +- views/layouts/welcome.hbs | 1 + views/login.hbs | 10 +- views/partials/fork_me.hbs | 10 -- views/partials/messages.hbs | 5 - views/partials/ui.hbs | 4 - views/partials/ui_docs_button.hbs | 9 -- views/partials/ui_node_anchor.hbs | 13 -- views/partials/ui_node_image.hbs | 6 - views/partials/ui_node_input_button.hbs | 18 --- views/partials/ui_node_input_checkbox.hbs | 26 ---- views/partials/ui_node_input_default.hbs | 22 --- views/partials/ui_node_input_hidden.hbs | 4 - views/partials/ui_node_input_password.hbs | 9 -- .../partials/ui_node_input_social_button.hbs | 18 --- views/partials/ui_node_script.hbs | 9 -- views/partials/ui_node_text.hbs | 25 --- views/partials/ui_nodes.hbs | 3 - views/partials/ui_screen_button.hbs | 21 --- views/partials/webauthn_setup.hbs | 21 +++ views/registration.hbs | 10 +- views/settings.hbs | 26 +--- 42 files changed, 188 insertions(+), 519 deletions(-) delete mode 100644 views/partials/fork_me.hbs delete mode 100644 views/partials/messages.hbs delete mode 100644 views/partials/ui.hbs delete mode 100644 views/partials/ui_docs_button.hbs delete mode 100644 views/partials/ui_node_anchor.hbs delete mode 100644 views/partials/ui_node_image.hbs delete mode 100644 views/partials/ui_node_input_button.hbs delete mode 100644 views/partials/ui_node_input_checkbox.hbs delete mode 100644 views/partials/ui_node_input_default.hbs delete mode 100644 views/partials/ui_node_input_hidden.hbs delete mode 100644 views/partials/ui_node_input_password.hbs delete mode 100644 views/partials/ui_node_input_social_button.hbs delete mode 100644 views/partials/ui_node_script.hbs delete mode 100644 views/partials/ui_node_text.hbs delete mode 100644 views/partials/ui_nodes.hbs delete mode 100644 views/partials/ui_screen_button.hbs create mode 100644 views/partials/webauthn_setup.hbs diff --git a/package-lock.json b/package-lock.json index 1c6652be..7cafb860 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,9 @@ "license": "Apache-2.0", "dependencies": { "@ory/client": "1.1.50", - "@ory/elements-markup": "0.1.0-beta.1", + "@ory/elements-markup": "0.1.0-beta.2", "@ory/integrations": "1.1.4", + "accept-language-parser": "1.5.0", "axios": "1.2.6", "body-parser": "1.20.2", "cookie-parser": "1.4.6", @@ -25,6 +26,7 @@ }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "4.0.0", + "@types/accept-language-parser": "^1.5.3", "@types/axios": "0.14.0", "@types/body-parser": "1.19.2", "@types/cookie-parser": "1.4.3", @@ -786,9 +788,9 @@ } }, "node_modules/@ory/elements-markup": { - "version": "0.1.0-beta.1", - "resolved": "https://registry.npmjs.org/@ory/elements-markup/-/elements-markup-0.1.0-beta.1.tgz", - "integrity": "sha512-9IflMA7F8UGWMzCWW4M+BdS512h4yki7vNo+bZRtbqp20Vy8oLXFd2sr1BmzN+LVxNt3H66LTy/zgw/Vj0Svnw==", + "version": "0.1.0-beta.2", + "resolved": "https://registry.npmjs.org/@ory/elements-markup/-/elements-markup-0.1.0-beta.2.tgz", + "integrity": "sha512-TG+S5ZDxH82HV72Vt1hJ3b0Drlw2qqBUzta2Vf0GyuFc09SXTuO2snlkwQiGRvRNB5j/2VTFENQKHZNKQ4NxrA==", "engines": { "node": ">=16.16.0", "npm": ">=8.11.0" @@ -944,6 +946,12 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "dev": true }, + "node_modules/@types/accept-language-parser": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@types/accept-language-parser/-/accept-language-parser-1.5.3.tgz", + "integrity": "sha512-S8oM29O6nnRC3/+rwYV7GBYIIgNIZ52PCxqBG7OuItq9oATnYWy8FfeLKwvq5F7pIYjeeBSCI7y+l+Z9UEQpVQ==", + "dev": true + }, "node_modules/@types/axios": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@types/axios/-/axios-0.14.0.tgz", @@ -1232,6 +1240,11 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "node_modules/accept-language-parser": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/accept-language-parser/-/accept-language-parser-1.5.0.tgz", + "integrity": "sha512-QhyTbMLYo0BBGg1aWbeMG4ekWtds/31BrEU+DONOg/7ax23vxpL03Pb7/zBmha2v7vdD3AyzZVWBVGEZxKOXWw==" + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", diff --git a/package.json b/package.json index 228d30fc..5f5d1973 100644 --- a/package.json +++ b/package.json @@ -25,10 +25,12 @@ "format:check": "prettier --check .", "prepublishOnly": "npm run build" }, + "prettier": "ory-prettier-styles", "dependencies": { "@ory/client": "1.1.50", - "@ory/elements-markup": "0.1.0-beta.1", + "@ory/elements-markup": "0.1.0-beta.2", "@ory/integrations": "1.1.4", + "accept-language-parser": "1.5.0", "axios": "1.2.6", "body-parser": "1.20.2", "cookie-parser": "1.4.6", @@ -42,6 +44,7 @@ }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "4.0.0", + "@types/accept-language-parser": "^1.5.3", "@types/axios": "0.14.0", "@types/body-parser": "1.19.2", "@types/cookie-parser": "1.4.3", diff --git a/src/index.ts b/src/index.ts index e74a9e91..18a5c366 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,11 +1,11 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import cookieParser from "cookie-parser" -import express, { Request, Response } from "express" -import { engine } from "express-handlebars" -import * as fs from "fs" -import * as https from "https" -import { addFavicon, defaultConfig, handlebarsHelpers } from "./pkg" +import { + addFavicon, + defaultConfig, + detectLanguage, + handlebarsHelpers, +} from "./pkg" import { middleware as middlewareLogger } from "./pkg/logger" import { register404Route, @@ -23,6 +23,11 @@ import { registerVerificationRoute, registerWelcomeRoute, } from "./routes" +import cookieParser from "cookie-parser" +import express, { Request, Response } from "express" +import { engine } from "express-handlebars" +import * as fs from "fs" +import * as https from "https" const baseUrl = process.env.BASE_PATH || "/" @@ -32,6 +37,7 @@ const router = express.Router() app.use(middlewareLogger) app.use(cookieParser()) app.use(addFavicon(defaultConfig)) +app.use(detectLanguage) app.set("view engine", "hbs") app.engine( diff --git a/src/pkg/index.ts b/src/pkg/index.ts index 87c72bf9..491c6761 100644 --- a/src/pkg/index.ts +++ b/src/pkg/index.ts @@ -1,16 +1,15 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { RouteOptionsCreator } from "./route" +import sdk, { apiBaseUrl } from "./sdk" import { UiNode, ErrorAuthenticatorAssuranceLevelNotSatisfied, } from "@ory/client" import { ButtonLink, Divider, MenuLink, Typography } from "@ory/elements-markup" -import { filterNodesByGroups, getNodeLabel } from "@ory/integrations/ui" +import { filterNodesByGroups } from "@ory/integrations/ui" import { AxiosError } from "axios" import { NextFunction, Response } from "express" -import { RouteOptionsCreator } from "./route" -import sdk, { apiBaseUrl } from "./sdk" -import { toUiNodePartial } from "./ui" export * from "./logger" export * from "./middleware" @@ -101,8 +100,6 @@ export const handlebarsHelpers = { withoutDefaultAttributes, withoutDefaultGroup, }), - toUiNodePartial, - getNodeLabel: getNodeLabel, divider: (fullWidth: boolean, className?: string) => Divider({ className, fullWidth }), buttonLink: (text: string) => diff --git a/src/pkg/middleware.ts b/src/pkg/middleware.ts index ce889b29..75b3ec1a 100644 --- a/src/pkg/middleware.ts +++ b/src/pkg/middleware.ts @@ -1,16 +1,19 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { getUrlForFlow, isUUID } from "./index" +import { RouteOptionsCreator } from "./route" import { Session } from "@ory/client" +import { locales } from "@ory/elements-markup" +import { pick as pickLanguage } from "accept-language-parser" import { AxiosError } from "axios" import { NextFunction, Request, Response } from "express" -import { getUrlForFlow, isUUID } from "./index" -import { RouteOptionsCreator } from "./route" /** * Checks the error returned by toSession() and initiates a 2FA flow if necessary * or returns false. * * @internal + * @param req * @param res * @param apiBaseUrl */ @@ -125,3 +128,22 @@ export const requireNoAuth = next() }) } + +/** + * Detects the language of the user and sets it in the response locals. + * + * @param req + * @param res + * @param next + */ +export const detectLanguage = ( + req: Request, + res: Response, + next: NextFunction, +) => { + res.locals.lang = pickLanguage( + Object.keys(locales), + req.header("Accept-Language") || "en", + ) + next() +} diff --git a/src/pkg/ui.ts b/src/pkg/ui.ts index 5403705a..0c53b8eb 100644 --- a/src/pkg/ui.ts +++ b/src/pkg/ui.ts @@ -10,38 +10,6 @@ import { isUiNodeTextAttributes, } from "@ory/integrations/ui" -// This helper function translates the html input type to the corresponding partial name. -export const toUiNodePartial = (node: UiNode) => { - if (isUiNodeAnchorAttributes(node.attributes)) { - return "ui_node_anchor" - } else if (isUiNodeImageAttributes(node.attributes)) { - return "ui_node_image" - } else if (isUiNodeInputAttributes(node.attributes)) { - switch (node.attributes && node.attributes.type) { - case "hidden": - return "ui_node_input_hidden" - case "submit": - const attrs = node.attributes as UiNodeInputAttributes - const isSocial = - (attrs.name === "provider" || attrs.name === "link") && - node.group === "oidc" - - return isSocial ? "ui_node_input_social_button" : "ui_node_input_button" - case "button": - return "ui_node_input_button" - case "checkbox": - return "ui_node_input_checkbox" - default: - return "ui_node_input_default" - } - } else if (isUiNodeScriptAttributes(node.attributes)) { - return "ui_node_script" - } else if (isUiNodeTextAttributes(node.attributes)) { - return "ui_node_text" - } - return "ui_node_input_default" -} - type NavigationMenuProps = { navTitle: string session?: Session diff --git a/src/routes/404.ts b/src/routes/404.ts index 661a00cc..d5fa6b61 100644 --- a/src/routes/404.ts +++ b/src/routes/404.ts @@ -1,7 +1,7 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { UserErrorCard } from "@ory/elements-markup" import { RouteRegistrator } from "../pkg" +import { UserErrorCard } from "@ory/elements-markup" export const register404Route: RouteRegistrator = (app, createHelpers) => { app.get("*", (req, res) => { diff --git a/src/routes/500.ts b/src/routes/500.ts index e39d29a1..05daadf9 100644 --- a/src/routes/500.ts +++ b/src/routes/500.ts @@ -1,8 +1,8 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { RouteRegistrator } from "../pkg" import { UserErrorCard } from "@ory/elements-markup" import { NextFunction, Request, Response } from "express" -import { RouteRegistrator } from "../pkg" export const register500Route: RouteRegistrator = (app, createHelpers) => { app.use((err: Error, req: Request, res: Response, next: NextFunction) => { diff --git a/src/routes/consent.ts b/src/routes/consent.ts index b741500e..a39613a3 100644 --- a/src/routes/consent.ts +++ b/src/routes/consent.ts @@ -1,5 +1,8 @@ // Copyright © 2023 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { defaultConfig, RouteCreator, RouteRegistrator } from "../pkg" +import { register404Route } from "./404" +import { oidcConformityMaybeFakeSession } from "./stub/oidc-cert" import { AcceptOAuth2ConsentRequestSession, IdentityApi, @@ -8,9 +11,6 @@ import { import { UserConsentCard } from "@ory/elements-markup" import bodyParser from "body-parser" import csrf from "csurf" -import { defaultConfig, RouteCreator, RouteRegistrator } from "../pkg" -import { register404Route } from "./404" -import { oidcConformityMaybeFakeSession } from "./stub/oidc-cert" async function createOAuth2ConsentRequestSession( grantScopes: string[], diff --git a/src/routes/error.ts b/src/routes/error.ts index bb36d9d6..ce6078a2 100644 --- a/src/routes/error.ts +++ b/src/routes/error.ts @@ -1,14 +1,14 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { FlowError, FrontendApi, GenericError } from "@ory/client" -import { UserErrorCard } from "@ory/elements-markup" -import { isAxiosError } from "axios" import { RouteCreator, RouteRegistrator, defaultConfig, isQuerySet, } from "../pkg" +import { FlowError, FrontendApi, GenericError } from "@ory/client" +import { UserErrorCard } from "@ory/elements-markup" +import { isAxiosError } from "axios" type OAuth2Error = { error: string @@ -78,7 +78,6 @@ export const createErrorRoute: RouteCreator = card: UserErrorCard({ error, cardImage: logoUrl, - title: "An error occurred", backUrl: req.header("Referer") || "welcome", }), }) diff --git a/src/routes/health.ts b/src/routes/health.ts index 3ee29528..42b461f5 100644 --- a/src/routes/health.ts +++ b/src/routes/health.ts @@ -1,7 +1,7 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { Request, Response } from "express" import { RouteRegistrator } from "../pkg" +import { Request, Response } from "express" export const registerHealthRoute: RouteRegistrator = (app) => { app.get("/health/alive", (_: Request, res: Response) => res.send("ok")) diff --git a/src/routes/login.ts b/src/routes/login.ts index 497ab6c6..c97dc432 100644 --- a/src/routes/login.ts +++ b/src/routes/login.ts @@ -1,22 +1,22 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { LoginFlow, UiNodeInputAttributes } from "@ory/client" -import { SelfServiceFlow, UserAuthCard } from "@ory/elements-markup" import { - filterNodesByGroups, - isUiNodeInputAttributes, -} from "@ory/integrations/ui" -import path from "path" -import { URLSearchParams } from "url" -import { - RouteCreator, - RouteRegistrator, defaultConfig, getUrlForFlow, isQuerySet, logger, redirectOnSoftError, + RouteCreator, + RouteRegistrator, } from "../pkg" +import { LoginFlow, UiNodeInputAttributes } from "@ory/client" +import { UserAuthCard } from "@ory/elements-markup" +import { + filterNodesByGroups, + isUiNodeInputAttributes, +} from "@ory/integrations/ui" +import path from "path" +import { URLSearchParams } from "url" export const createLoginRoute: RouteCreator = (createHelpers) => async (req, res, next) => { @@ -67,10 +67,9 @@ export const createLoginRoute: RouteCreator = (return_to && return_to.toString()) || loginFlow.return_to || "", }) .then(({ data }) => data.logout_url) + return logoutUrl } catch (err) { logger.error("Unable to create logout URL", { error: err }) - } finally { - return logoutUrl } } @@ -162,7 +161,7 @@ export const createLoginRoute: RouteCreator = ) } - let logoutUrl = "" + let logoutUrl: string | undefined = "" if (flow.requested_aal === "aal2" || flow.refresh) { logoutUrl = await getLogoutUrl(flow) } @@ -181,27 +180,19 @@ export const createLoginRoute: RouteCreator = return (attributes as UiNodeInputAttributes).onclick }) .filter((c) => c !== undefined), - card: UserAuthCard({ - title: flow.refresh - ? "Confirm it's you" - : flow.requested_aal === "aal2" - ? "Two-Factor Authentication" - : "Sign In", - ...(flow.oauth2_login_request && { - subtitle: `To authenticate ${ - flow.oauth2_login_request.client?.client_name || - flow.oauth2_login_request.client?.client_id - }`, - }), - flow: flow, - flowType: "login", - cardImage: logoUrl, - additionalProps: { - forgotPasswordURL: initRecoveryUrl, - signupURL: initRegistrationUrl, - logoutURL: logoutUrl, + card: UserAuthCard( + { + flow, + flowType: "login", + cardImage: logoUrl, + additionalProps: { + forgotPasswordURL: initRecoveryUrl, + signupURL: initRegistrationUrl, + logoutURL: logoutUrl, + }, }, - }), + { locale: res.locals.lang }, + ), }) }) .catch(redirectOnSoftError(res, next, initFlowUrl)) diff --git a/src/routes/recovery.ts b/src/routes/recovery.ts index 40a7c7f8..0b7872af 100644 --- a/src/routes/recovery.ts +++ b/src/routes/recovery.ts @@ -1,6 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { SelfServiceFlow, UserAuthCard } from "@ory/elements-markup" import { defaultConfig, getUrlForFlow, @@ -11,6 +10,7 @@ import { RouteCreator, RouteRegistrator, } from "../pkg" +import { UserAuthCard } from "@ory/elements-markup" export const createRecoveryRoute: RouteCreator = (createHelpers) => (req, res, next) => { @@ -50,15 +50,19 @@ export const createRecoveryRoute: RouteCreator = ) res.render("recovery", { - card: UserAuthCard({ - title: "Recover your account", - flow: flow, - flowType: "recovery", - cardImage: logoUrl, - additionalProps: { - loginURL: initLoginUrl, + card: UserAuthCard( + { + flow, + flowType: "recovery", + cardImage: logoUrl, + additionalProps: { + loginURL: initLoginUrl, + }, }, - }), + { + locale: res.locals.lang, + }, + ), }) }) .catch(redirectOnSoftError(res, next, initFlowUrl)) diff --git a/src/routes/registration.ts b/src/routes/registration.ts index 21b6fc03..b9751b0e 100644 --- a/src/routes/registration.ts +++ b/src/routes/registration.ts @@ -1,11 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { UiNodeInputAttributes } from "@ory/client" -import { UserAuthCard } from "@ory/elements-markup" -import { - filterNodesByGroups, - isUiNodeInputAttributes, -} from "@ory/integrations/ui" import { defaultConfig, getUrlForFlow, @@ -16,6 +10,12 @@ import { RouteCreator, RouteRegistrator, } from "../pkg" +import { UiNodeInputAttributes } from "@ory/client" +import { UserAuthCard } from "@ory/elements-markup" +import { + filterNodesByGroups, + isUiNodeInputAttributes, +} from "@ory/integrations/ui" // A simple express handler that shows the registration screen. export const createRegistrationRoute: RouteCreator = @@ -86,25 +86,21 @@ export const createRegistrationRoute: RouteCreator = return (attributes as UiNodeInputAttributes).onclick }) .filter((onClickAction) => !!onClickAction), - card: UserAuthCard({ - title: "Register an account", - flow: flow, - ...(flow.oauth2_login_request && { - subtitle: `To authenticate ${ - flow.oauth2_login_request.client?.client_name || - flow.oauth2_login_request.client?.client_id - }`, - }), - flowType: "registration", - cardImage: logoUrl, - additionalProps: { - loginURL: getUrlForFlow( - kratosBrowserUrl, - "login", - initLoginQuery, - ), + card: UserAuthCard( + { + flow, + flowType: "registration", + cardImage: logoUrl, + additionalProps: { + loginURL: getUrlForFlow( + kratosBrowserUrl, + "login", + initLoginQuery, + ), + }, }, - }), + { locale: res.locals.lang }, + ), }) }) .catch(redirectOnSoftError(res, next, initFlowUrl)) diff --git a/src/routes/sessions.ts b/src/routes/sessions.ts index 9cc50d78..08f5e526 100644 --- a/src/routes/sessions.ts +++ b/src/routes/sessions.ts @@ -1,6 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { CodeBox, Typography } from "@ory/elements-markup" import { defaultConfig, requireAuth, @@ -8,6 +7,7 @@ import { RouteRegistrator, } from "../pkg" import { navigationMenu } from "../pkg/ui" +import { CodeBox, Typography } from "@ory/elements-markup" export const createSessionsRoute: RouteCreator = (createHelpers) => async (req, res) => { @@ -44,7 +44,7 @@ export const createSessionsRoute: RouteCreator = id: session?.identity.id, // sometimes the identity schema could contain recursive objects // for this use case we will just stringify the object instead of recursively flatten the object - ...Object.entries(session?.identity.traits).reduce( + ...Object.entries(session?.identity.traits).reduce>( (traits, [key, value]) => { traits[key] = typeof value === "object" ? JSON.stringify(value) : value diff --git a/src/routes/settings.ts b/src/routes/settings.ts index 17ec382d..86d62cb6 100644 --- a/src/routes/settings.ts +++ b/src/routes/settings.ts @@ -1,24 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { UiNodeInputAttributes } from "@ory/client" -import { - Divider, - hasLookupSecret, - hasOidc, - hasPassword, - hasTotp, - hasWebauthn, - Nav, - NavSectionLinks, - NodeMessages, - Typography, - UserSettingsCard, - UserSettingsFlowType, -} from "@ory/elements-markup" -import { - filterNodesByGroups, - isUiNodeInputAttributes, -} from "@ory/integrations/ui" import { defaultConfig, getUrlForFlow, @@ -29,6 +10,7 @@ import { RouteCreator, RouteRegistrator, } from "../pkg" +import { UserSettingsScreen } from "@ory/elements-markup" export const createSettingsRoute: RouteCreator = (createHelpers) => async (req, res, next) => { @@ -53,16 +35,6 @@ export const createSettingsRoute: RouteCreator = return } - const session = req.session - - const identityCredentialTrait = - session?.identity.traits.email || session?.identity.traits.username || "" - - const sessionText = - identityCredentialTrait !== "" - ? `You are currently logged in as ${identityCredentialTrait} ` - : "" - return frontend .getSettingsFlow({ id: flow, cookie: req.header("cookie") }) .then(async ({ data: flow }) => { @@ -76,115 +48,25 @@ export const createSettingsRoute: RouteCreator = .then(({ data }) => data.logout_url) .catch(() => "")) || "" - const conditionalLinks: NavSectionLinks[] = [ + const settingsScreen = UserSettingsScreen( { - name: "Profile", - href: "#profile", - iconLeft: "user", - testId: "profile", - }, - hasPassword(flow.ui.nodes) && { - name: "Password", - href: "#password", - iconLeft: "lock", - testId: "password", - }, - hasOidc(flow.ui.nodes) && { - name: "Social Sign In", - href: "#oidc", - iconLeft: "comments", - testId: "social-sign-in", + flow, + logoutUrl, + navClassName: "main-nav", + headerContainerClassName: "spacing-32", + dividerClassName: "divider-left", + settingsCardContainerClassName: "spacing-32", }, - hasLookupSecret(flow.ui.nodes) && { - name: "2FA Backup Codes", - href: "#lookupSecret", - iconLeft: "shield", - testId: "backup-codes", - }, - hasWebauthn(flow.ui.nodes) && { - name: "Hardware Tokens", - href: "#webauthn", - iconLeft: "key", - testId: "webauthn", - }, - hasTotp(flow.ui.nodes) && { - name: "Authenticator App", - href: "#totp", - iconLeft: "mobile", - testId: "totp", + { + locale: res.locals.lang, }, - ].filter(Boolean) as NavSectionLinks[] - + ) // Render the data using a view (e.g. Jade Template): res.render("settings", { layout: "settings", - nav: Nav({ - className: "main-nav", - navTitle: res.locals.projectName, - navSections: [ - { - links: conditionalLinks, - }, - { - links: [ - { - name: "Logout", - href: logoutUrl, - iconLeft: "arrow-right-to-bracket", - testId: "logout", - }, - ], - }, - ], - }), nodes: flow.ui.nodes, - errorMessages: NodeMessages({ - uiMessages: flow.ui.messages, - textPosition: "start", - }), - sessionDescription: [ - sessionText !== "" && - Typography({ - children: sessionText, - color: "foregroundMuted", - size: "small", - }), - Typography({ - children: - "Here you can manage settings related to your account. Keep in mind that certain actions require you to re-authenticate.", - color: "foregroundMuted", - size: "small", - }), - ] - .filter(Boolean) - .join(""), - webAuthnHandler: filterNodesByGroups({ - nodes: flow.ui.nodes, - groups: "webauthn", - attributes: "button", - withoutDefaultAttributes: true, - withoutDefaultGroup: true, - }) - .filter(({ attributes }) => isUiNodeInputAttributes(attributes)) - .map(({ attributes }) => { - return (attributes as UiNodeInputAttributes).onclick - }) - .filter((c) => c !== undefined), - settingsCard: (flowType: string) => { - const card = UserSettingsCard({ - flow, - flowType: flowType as UserSettingsFlowType, - }) - if (card) { - return ( - `
` + - card + - Divider({ className: "divider-left", fullWidth: false }) + - `
` - ) - } - return "" - }, + nav: settingsScreen.Nav, + settingsScreen: settingsScreen.Body, }) }) .catch(redirectOnSoftError(res, next, initFlowUrl)) diff --git a/src/routes/static.ts b/src/routes/static.ts index d844d94c..91a4a508 100644 --- a/src/routes/static.ts +++ b/src/routes/static.ts @@ -1,12 +1,12 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 +import { defaultConfig, RouteRegistrator } from "../pkg" +import sdk from "../pkg/sdk" import { defaultLightTheme, RegisterOryElementsExpress, } from "@ory/elements-markup" import express from "express" -import { defaultConfig, RouteRegistrator } from "../pkg" -import sdk from "../pkg/sdk" export const registerStaticRoutes: RouteRegistrator = ( app, diff --git a/src/routes/verification.ts b/src/routes/verification.ts index cab4b879..25289d97 100644 --- a/src/routes/verification.ts +++ b/src/routes/verification.ts @@ -1,7 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { UiText } from "@ory/client" -import { SelfServiceFlow, UserAuthCard } from "@ory/elements-markup" import { defaultConfig, getUrlForFlow, @@ -11,6 +9,8 @@ import { RouteCreator, RouteRegistrator, } from "../pkg" +import { UiText } from "@ory/client" +import { UserAuthCard } from "@ory/elements-markup" export const createVerificationRoute: RouteCreator = (createHelpers) => (req, res, next) => { @@ -58,15 +58,17 @@ export const createVerificationRoute: RouteCreator = // Render the data using a view (e.g. Jade Template): res.render("verification", { - card: UserAuthCard({ - title: "Verify your account", - flow: flow, - flowType: "verification", - cardImage: logoUrl, - additionalProps: { - signupURL: initRegistrationUrl, + card: UserAuthCard( + { + flow, + flowType: "verification", + cardImage: logoUrl, + additionalProps: { + signupURL: initRegistrationUrl, + }, }, - }), + { locale: res.locals.lang }, + ), }) }) // Handle errors using ExpressJS' next functionality: diff --git a/src/routes/welcome.ts b/src/routes/welcome.ts index 8d6c524b..179cf6fe 100644 --- a/src/routes/welcome.ts +++ b/src/routes/welcome.ts @@ -1,6 +1,5 @@ // Copyright © 2022 Ory Corp // SPDX-License-Identifier: Apache-2.0 -import { CardGradient, Typography } from "@ory/elements-markup" import { defaultConfig, RouteCreator, @@ -8,6 +7,7 @@ import { setSession, } from "../pkg" import { navigationMenu } from "../pkg/ui" +import { CardGradient, Typography } from "@ory/elements-markup" export const createWelcomeRoute: RouteCreator = (createHelpers) => async (req, res) => { diff --git a/views/layouts/auth.hbs b/views/layouts/auth.hbs index 39983c9d..0f4f435c 100644 --- a/views/layouts/auth.hbs +++ b/views/layouts/auth.hbs @@ -1,4 +1,5 @@ - + + {{> standard_headers}} @@ -9,7 +10,7 @@ {{{body}}} {{> ory_branding}} - + {{> scripts }} diff --git a/views/layouts/settings.hbs b/views/layouts/settings.hbs index cb5b12f3..058c3cc9 100644 --- a/views/layouts/settings.hbs +++ b/views/layouts/settings.hbs @@ -1,4 +1,5 @@ - + + {{> standard_headers}} diff --git a/views/layouts/welcome.hbs b/views/layouts/welcome.hbs index cb5b12f3..43757463 100644 --- a/views/layouts/welcome.hbs +++ b/views/layouts/welcome.hbs @@ -1,3 +1,4 @@ + {{> standard_headers}} diff --git a/views/login.hbs b/views/login.hbs index 901f5e0b..c2d013a6 100644 --- a/views/login.hbs +++ b/views/login.hbs @@ -1,13 +1,5 @@
{{{card}}} - - {{> ui_nodes nodes=nodes groups="webauthn" attributes="text/javascript" withoutDefaultGroup=true}} - + {{> webauthn_setup nodes=nodes webAuthnHandler=webAuthnHandler webauthnTriggerName="webauthn_login_trigger"}}
diff --git a/views/partials/fork_me.hbs b/views/partials/fork_me.hbs deleted file mode 100644 index f1f15f7d..00000000 --- a/views/partials/fork_me.hbs +++ /dev/null @@ -1,10 +0,0 @@ -{{{divider true "footer-divider"}}} - \ No newline at end of file diff --git a/views/partials/messages.hbs b/views/partials/messages.hbs deleted file mode 100644 index b28d6091..00000000 --- a/views/partials/messages.hbs +++ /dev/null @@ -1,5 +0,0 @@ -
- {{~#each messages~}} -
{{text}}
- {{~/each~}} -
diff --git a/views/partials/ui.hbs b/views/partials/ui.hbs deleted file mode 100644 index 4852c7be..00000000 --- a/views/partials/ui.hbs +++ /dev/null @@ -1,4 +0,0 @@ -
- {{> messages messages=ui.messages }} - {{> ui_nodes nodes=ui.nodes groups=groups attributes=attributes }} -
diff --git a/views/partials/ui_docs_button.hbs b/views/partials/ui_docs_button.hbs deleted file mode 100644 index 0a535a03..00000000 --- a/views/partials/ui_docs_button.hbs +++ /dev/null @@ -1,9 +0,0 @@ -
-
-
- {{label}} -
-
-
diff --git a/views/partials/ui_node_anchor.hbs b/views/partials/ui_node_anchor.hbs deleted file mode 100644 index 053a8baf..00000000 --- a/views/partials/ui_node_anchor.hbs +++ /dev/null @@ -1,13 +0,0 @@ -
- - {{attributes.title.text}} - - {{#if messages}} - - {{> messages }} - - {{/if}} -
diff --git a/views/partials/ui_node_image.hbs b/views/partials/ui_node_image.hbs deleted file mode 100644 index 11b8a5b6..00000000 --- a/views/partials/ui_node_image.hbs +++ /dev/null @@ -1,6 +0,0 @@ -{{getNodeLabel . }} diff --git a/views/partials/ui_node_input_button.hbs b/views/partials/ui_node_input_button.hbs deleted file mode 100644 index eda1605b..00000000 --- a/views/partials/ui_node_input_button.hbs +++ /dev/null @@ -1,18 +0,0 @@ -
- - {{#if messages}} - - {{> messages }} - - {{/if}} -
- diff --git a/views/partials/ui_node_input_checkbox.hbs b/views/partials/ui_node_input_checkbox.hbs deleted file mode 100644 index fcd212a6..00000000 --- a/views/partials/ui_node_input_checkbox.hbs +++ /dev/null @@ -1,26 +0,0 @@ -
-
- - - -
- {{#if messages}} -
- {{> messages }} -
- {{/if}} -
diff --git a/views/partials/ui_node_input_default.hbs b/views/partials/ui_node_input_default.hbs deleted file mode 100644 index 56fbb4c2..00000000 --- a/views/partials/ui_node_input_default.hbs +++ /dev/null @@ -1,22 +0,0 @@ -
- - {{#if messages}} -
- {{> messages }} -
- {{/if}} -
diff --git a/views/partials/ui_node_input_hidden.hbs b/views/partials/ui_node_input_hidden.hbs deleted file mode 100644 index cc0d95bb..00000000 --- a/views/partials/ui_node_input_hidden.hbs +++ /dev/null @@ -1,4 +0,0 @@ - diff --git a/views/partials/ui_node_input_password.hbs b/views/partials/ui_node_input_password.hbs deleted file mode 100644 index 7970cb92..00000000 --- a/views/partials/ui_node_input_password.hbs +++ /dev/null @@ -1,9 +0,0 @@ -
- - {{> messages }} -
diff --git a/views/partials/ui_node_input_social_button.hbs b/views/partials/ui_node_input_social_button.hbs deleted file mode 100644 index a9c2ed56..00000000 --- a/views/partials/ui_node_input_social_button.hbs +++ /dev/null @@ -1,18 +0,0 @@ -
- - {{#if messages}} - - {{> messages }} - - {{/if}} -
diff --git a/views/partials/ui_node_script.hbs b/views/partials/ui_node_script.hbs deleted file mode 100644 index 42f86ece..00000000 --- a/views/partials/ui_node_script.hbs +++ /dev/null @@ -1,9 +0,0 @@ - diff --git a/views/partials/ui_node_text.hbs b/views/partials/ui_node_text.hbs deleted file mode 100644 index f1fb06ac..00000000 --- a/views/partials/ui_node_text.hbs +++ /dev/null @@ -1,25 +0,0 @@ -
-

- {{getNodeLabel .}} -

- {{#if (eq attributes.text.id 1050015)}} - -
-
- {{#each attributes.text.context.secrets}} - -
- {{#if (eq id 1050014)}}Used{{else}}{{text}}{{/if}}
- {{/each}} -
- -
- {{else}} -
{{attributes.text.text}}
- {{/if}} -
diff --git a/views/partials/ui_nodes.hbs b/views/partials/ui_nodes.hbs deleted file mode 100644 index 6761c6f0..00000000 --- a/views/partials/ui_nodes.hbs +++ /dev/null @@ -1,3 +0,0 @@ -{{#each (onlyNodes nodes groups attributes)}} - {{> (toUiNodePartial .)}} -{{/each}} diff --git a/views/partials/ui_screen_button.hbs b/views/partials/ui_screen_button.hbs deleted file mode 100644 index 7c4978f0..00000000 --- a/views/partials/ui_screen_button.hbs +++ /dev/null @@ -1,21 +0,0 @@ -
-
- -
-
diff --git a/views/partials/webauthn_setup.hbs b/views/partials/webauthn_setup.hbs new file mode 100644 index 00000000..4abe9a81 --- /dev/null +++ b/views/partials/webauthn_setup.hbs @@ -0,0 +1,21 @@ +{{#each (onlyNodes nodes "webauthn" "text/javascript")}} + +{{/each}} + + diff --git a/views/registration.hbs b/views/registration.hbs index 103ca6cd..9dbdfe77 100644 --- a/views/registration.hbs +++ b/views/registration.hbs @@ -2,13 +2,5 @@ {{{card}}} - {{> ui_nodes nodes=nodes groups="webauthn" attributes="text/javascript" withoutDefaultGroup=true}} - - + {{> webauthn_setup nodes=nodes webAuthnHandler=webAuthnHandler webauthnTriggerName="webauthn_register_trigger"}} diff --git a/views/settings.hbs b/views/settings.hbs index dfc9d1b7..ab1cf79c 100644 --- a/views/settings.hbs +++ b/views/settings.hbs @@ -1,29 +1,9 @@
-
- {{{typography "Account Settings" "headline37" "foregroundDefault"}}} -
- {{{sessionDescription}}} +
+ {{{settingsScreen}}}
- {{{errorMessages}}} - {{{divider false "divider-left"}}} -
- {{{settingsCard "profile"}}} - {{{settingsCard "password"}}} - {{{settingsCard "oidc"}}} - {{{settingsCard "lookupSecret"}}} - {{{settingsCard "webauthn"}}} - {{{settingsCard "totp"}}} - - {{> ui_nodes nodes=nodes groups="webauthn" attributes="text/javascript" withoutDefaultGroup=true}} - - + {{> webauthn_setup nodes=nodes webAuthnHandler=webAuthnHandler webauthnTriggerName="webauthn_settings_trigger"}}