From 0df2e648c8e47f1a896964d60288de96d3a00396 Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Fri, 16 Aug 2024 15:20:02 +0800 Subject: [PATCH 1/8] Add dummy EditConfigurationScreen --- portal/src/AppRoot.tsx | 13 +++++++++++++ .../src/graphql/portal/EditConfigurationScreen.tsx | 7 +++++++ 2 files changed, 20 insertions(+) create mode 100644 portal/src/graphql/portal/EditConfigurationScreen.tsx diff --git a/portal/src/AppRoot.tsx b/portal/src/AppRoot.tsx index 91523cb4b7..a2dab4d03c 100644 --- a/portal/src/AppRoot.tsx +++ b/portal/src/AppRoot.tsx @@ -8,6 +8,7 @@ import ScreenLayout from "./ScreenLayout"; import ShowLoading from "./ShowLoading"; import CookieLifetimeConfigurationScreen from "./graphql/portal/CookieLifetimeConfigurationScreen"; import { useUnauthenticatedDialogContext } from "./components/auth/UnauthenticatedDialogContext"; +import EditConfigurationScreen from "./graphql/portal/EditConfigurationScreen"; const RolesScreen = lazy(async () => import("./graphql/adminapi/RolesScreen")); const AddRoleScreen = lazy( @@ -790,6 +791,18 @@ const AppRoot: React.VFC = function AppRoot() { } /> + + {/* This screen is not shown in nav bar, which is intentional to prevent normal users from accessing it */} + + }> + + + } + /> + diff --git a/portal/src/graphql/portal/EditConfigurationScreen.tsx b/portal/src/graphql/portal/EditConfigurationScreen.tsx new file mode 100644 index 0000000000..65446b9729 --- /dev/null +++ b/portal/src/graphql/portal/EditConfigurationScreen.tsx @@ -0,0 +1,7 @@ +import React from "react"; + +const EditConfigurationScreen: React.VFC = function EditConfigurationScreen() { + return <>dummy edit config screen; +}; + +export default EditConfigurationScreen; From b1179bbc3da9971e114bd9efed79ebbeb64267c5 Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Fri, 16 Aug 2024 16:17:54 +0800 Subject: [PATCH 2/8] Do not render edit templates widget title if null --- portal/src/graphql/portal/EditTemplatesWidget.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/portal/src/graphql/portal/EditTemplatesWidget.tsx b/portal/src/graphql/portal/EditTemplatesWidget.tsx index 35597161c2..27ca37a7dc 100644 --- a/portal/src/graphql/portal/EditTemplatesWidget.tsx +++ b/portal/src/graphql/portal/EditTemplatesWidget.tsx @@ -67,7 +67,9 @@ const EditTemplatesWidget: React.VFC = {sections.map((section) => { return ( - + {section.title != null ? ( + + ) : null} {section.items.map((item) => { return item.editor === "code" ? ( From bddf57f1855844bec46306f113888818b8764806 Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Fri, 16 Aug 2024 16:16:34 +0800 Subject: [PATCH 3/8] Render layout of edit project configuration screen --- .../portal/EditConfigurationScreen.module.css | 3 ++ .../portal/EditConfigurationScreen.tsx | 41 ++++++++++++++++++- .../graphql/portal/EditTemplatesWidget.tsx | 2 +- portal/src/locale-data/en.json | 2 + 4 files changed, 46 insertions(+), 2 deletions(-) create mode 100644 portal/src/graphql/portal/EditConfigurationScreen.module.css diff --git a/portal/src/graphql/portal/EditConfigurationScreen.module.css b/portal/src/graphql/portal/EditConfigurationScreen.module.css new file mode 100644 index 0000000000..0a8389da0d --- /dev/null +++ b/portal/src/graphql/portal/EditConfigurationScreen.module.css @@ -0,0 +1,3 @@ +.widget { + @apply col-span-8 tablet:col-span-full; +} diff --git a/portal/src/graphql/portal/EditConfigurationScreen.tsx b/portal/src/graphql/portal/EditConfigurationScreen.tsx index 65446b9729..5f0b37669b 100644 --- a/portal/src/graphql/portal/EditConfigurationScreen.tsx +++ b/portal/src/graphql/portal/EditConfigurationScreen.tsx @@ -1,7 +1,46 @@ import React from "react"; +import ScreenContent from "../../ScreenContent"; +import ScreenTitle from "../../ScreenTitle"; +import { FormattedMessage } from "@oursky/react-messageformat"; +import EditTemplatesWidget, { + EditTemplatesWidgetSection, +} from "./EditTemplatesWidget"; + +import styles from "./EditConfigurationScreen.module.css"; + +const SECTIONS_CONFIG_YAML: [EditTemplatesWidgetSection] = [ + { + key: "authgear.yaml", + title: null, + items: [ + { + key: "authgear.yaml", + title: null, + editor: "code", + language: "yaml", + + // TODO: implement value & onchange + value: "foobar", + onChange: () => {}, + }, + ], + }, +]; const EditConfigurationScreen: React.VFC = function EditConfigurationScreen() { - return <>dummy edit config screen; + return ( + <> + + + + + + + + ); }; export default EditConfigurationScreen; diff --git a/portal/src/graphql/portal/EditTemplatesWidget.tsx b/portal/src/graphql/portal/EditTemplatesWidget.tsx index 27ca37a7dc..55b3572b3d 100644 --- a/portal/src/graphql/portal/EditTemplatesWidget.tsx +++ b/portal/src/graphql/portal/EditTemplatesWidget.tsx @@ -40,7 +40,7 @@ const TextFieldWidgetItem: React.VFC = export interface EditTemplatesWidgetItem { key: string; title: React.ReactNode; - language: "html" | "plaintext" | "json" | "css"; + language: "html" | "plaintext" | "json" | "css" | "yaml"; editor: "code" | "textfield"; value: string; onChange: (value: string | undefined, e: unknown) => void; diff --git a/portal/src/locale-data/en.json b/portal/src/locale-data/en.json index bddfc1db10..e5b366c98e 100644 --- a/portal/src/locale-data/en.json +++ b/portal/src/locale-data/en.json @@ -1922,6 +1922,8 @@ "LoginMethodConfigurationScreen.passkey.title": "Enable passkey support for compatible devices. {DocLink, react, href{https://docs.authgear.com/strategies/passkeys} children{Learn more about passkey}}", "LoginMethodConfigurationScreen.combineLoginSignup.title": "Automatically signup a new user if a login ID is not found during login", + "EditConfigurationScreen.title": "Edit Project Configurations", + "LoginIDKeyType.email": "Email address", "LoginIDKeyType.phone": "Phone number", "LoginIDKeyType.username": "Username", From c5682b06f61f7fedae22266295d403cefa50c3cd Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Sun, 18 Aug 2024 03:26:10 +0800 Subject: [PATCH 4/8] Allow direct access on authgear.yaml After some archaeology, we realized that the blocking of direct access on authgear.yaml was of no particular reasons ref [PR #1399](https://github.com/authgear/authgear-server/pull/1399) --- pkg/portal/graphql/app.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/portal/graphql/app.go b/pkg/portal/graphql/app.go index 3de1cbd3c1..f934b8d3d7 100644 --- a/pkg/portal/graphql/app.go +++ b/pkg/portal/graphql/app.go @@ -214,9 +214,7 @@ var nodeApp = node( if argPaths, ok := p.Args["paths"]; ok { for _, path := range argPaths.([]interface{}) { path := path.(string) - if path == configsource.AuthgearYAML { - return nil, errors.New("direct access on authgear.yaml is disallowed") - } + // Note we do not block direct access to authgear.yaml if path == configsource.AuthgearSecretYAML { return nil, errors.New("direct access on authgear.secrets.yaml is disallowed") } From 12e7455e187bfd1bcde878d38a3d36d7690dcc0d Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Mon, 19 Aug 2024 16:39:45 +0800 Subject: [PATCH 5/8] Allow direct updates on authgear.yaml --- pkg/portal/graphql/app_mutation.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/pkg/portal/graphql/app_mutation.go b/pkg/portal/graphql/app_mutation.go index 023b5182be..1ecb8925a5 100644 --- a/pkg/portal/graphql/app_mutation.go +++ b/pkg/portal/graphql/app_mutation.go @@ -282,9 +282,6 @@ var _ = registerMutationField( for _, f := range updates { f := f.(map[string]interface{}) path := f["path"].(string) - if path == configsource.AuthgearYAML { - return nil, errors.New("direct update on authgear.yaml is disallowed") - } if path == configsource.AuthgearSecretYAML { return nil, errors.New("direct update on authgear.secrets.yaml is disallowed") } From e8068287f576e0cf349c9c108e6297d50436f471 Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Mon, 19 Aug 2024 15:52:32 +0800 Subject: [PATCH 6/8] Allow overriding codeEditor styles in EditTemplatesWidget --- portal/src/graphql/portal/EditTemplatesWidget.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/portal/src/graphql/portal/EditTemplatesWidget.tsx b/portal/src/graphql/portal/EditTemplatesWidget.tsx index 55b3572b3d..7a9e495597 100644 --- a/portal/src/graphql/portal/EditTemplatesWidget.tsx +++ b/portal/src/graphql/portal/EditTemplatesWidget.tsx @@ -55,12 +55,13 @@ export interface EditTemplatesWidgetSection { export interface EditTemplatesWidgetProps { className?: string; + codeEditorClassname?: string; sections: EditTemplatesWidgetSection[]; } const EditTemplatesWidget: React.VFC = function EditTemplatesWidget(props: EditTemplatesWidgetProps) { - const { className, sections } = props; + const { className, codeEditorClassname, sections } = props; return (
@@ -77,7 +78,7 @@ const EditTemplatesWidget: React.VFC = {item.title} Date: Mon, 19 Aug 2024 15:53:36 +0800 Subject: [PATCH 7/8] Implement editing authgear.yaml in EditConfig screen --- .../portal/EditConfigurationScreen.module.css | 4 + .../portal/EditConfigurationScreen.tsx | 154 +++++++++++++++--- portal/src/resources.ts | 7 + 3 files changed, 143 insertions(+), 22 deletions(-) diff --git a/portal/src/graphql/portal/EditConfigurationScreen.module.css b/portal/src/graphql/portal/EditConfigurationScreen.module.css index 0a8389da0d..9abbeaa0a6 100644 --- a/portal/src/graphql/portal/EditConfigurationScreen.module.css +++ b/portal/src/graphql/portal/EditConfigurationScreen.module.css @@ -1,3 +1,7 @@ .widget { @apply col-span-8 tablet:col-span-full; } + +.codeEditor { + height: 70vh; /* 680 / 960 ref https://www.figma.com/design/msiE4O5imHONAG5EjhZeiZ/Authgear-UI?node-id=10346-12112&t=ubpO59nWLEfmakyL-0 */ +} diff --git a/portal/src/graphql/portal/EditConfigurationScreen.tsx b/portal/src/graphql/portal/EditConfigurationScreen.tsx index 5f0b37669b..a2d8697047 100644 --- a/portal/src/graphql/portal/EditConfigurationScreen.tsx +++ b/portal/src/graphql/portal/EditConfigurationScreen.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useCallback, useMemo } from "react"; import ScreenContent from "../../ScreenContent"; import ScreenTitle from "../../ScreenTitle"; import { FormattedMessage } from "@oursky/react-messageformat"; @@ -7,39 +7,149 @@ import EditTemplatesWidget, { } from "./EditTemplatesWidget"; import styles from "./EditConfigurationScreen.module.css"; +import { useParams } from "react-router-dom"; +import ShowLoading from "../../ShowLoading"; +import ShowError from "../../ShowError"; +import FormContainer from "../../FormContainer"; +import { + ResourcesFormState, + useResourceForm, +} from "../../hook/useResourceForm"; +import { + expandSpecifier, + Resource, + ResourceSpecifier, + specifierId, +} from "../../util/resource"; +import { RESOURCE_AUTHGEAR_YAML } from "../../resources"; -const SECTIONS_CONFIG_YAML: [EditTemplatesWidgetSection] = [ - { - key: "authgear.yaml", - title: null, - items: [ - { - key: "authgear.yaml", - title: null, - editor: "code", - language: "yaml", - - // TODO: implement value & onchange - value: "foobar", - onChange: () => {}, - }, - ], - }, -]; +interface FormModel { + isLoading: boolean; + isUpdating: boolean; + isDirty: boolean; + loadError: unknown; + updateError: unknown; + state: FormState; + setState: (fn: (state: FormState) => FormState) => void; + reload: () => void; + reset: () => void; + save: () => Promise; +} + +interface FormState extends ResourcesFormState {} +const AUTHGEAR_YAML_RESOURCE_SPECIFIER: ResourceSpecifier = { + def: RESOURCE_AUTHGEAR_YAML, + locale: null, + extension: null, +}; const EditConfigurationScreen: React.VFC = function EditConfigurationScreen() { + const { appID } = useParams() as { appID: string }; + const specifiers = [AUTHGEAR_YAML_RESOURCE_SPECIFIER]; + const resourceForm = useResourceForm(appID, specifiers); + + const state = useMemo(() => { + return { + resources: resourceForm.state.resources, + }; + }, [resourceForm.state.resources]); + + const form: FormModel = useMemo( + () => ({ + isLoading: resourceForm.isLoading, + isUpdating: resourceForm.isUpdating, + isDirty: resourceForm.isDirty, + loadError: resourceForm.loadError, + updateError: resourceForm.updateError, + state, + setState: (fn) => { + const newState = fn(state); + resourceForm.setState(() => ({ resources: newState.resources })); + }, + reload: () => { + resourceForm.reload(); + }, + reset: () => { + resourceForm.reset(); + }, + save: async (ignoreConflict: boolean = false) => { + await resourceForm.save(ignoreConflict); + }, + }), + [resourceForm, state] + ); + + const rawAuthgearYAML = useMemo(() => { + const resource = + form.state.resources[specifierId(AUTHGEAR_YAML_RESOURCE_SPECIFIER)]; + if (resource == null) { + return null; + } + if (resource.nullableValue == null) { + return null; + } + return resource.nullableValue; + }, [form.state.resources]); + + const onChange = useCallback( + (value: string | undefined, _e: unknown) => { + const resource: Resource = { + specifier: AUTHGEAR_YAML_RESOURCE_SPECIFIER, + path: expandSpecifier(AUTHGEAR_YAML_RESOURCE_SPECIFIER), + nullableValue: value, + effectiveData: value, + }; + const updatedResources = { + [specifierId(AUTHGEAR_YAML_RESOURCE_SPECIFIER)]: resource, + }; + form.setState(() => { + return { + resources: updatedResources, + }; + }); + }, + [form] + ); + + if (form.isLoading) { + return ; + } + + if (form.loadError) { + return ; + } + + const authgearYAMLSections: [EditTemplatesWidgetSection] = [ + { + key: "authgear.yaml", + title: null, + items: [ + { + key: "authgear.yaml", + title: null, + editor: "code", + language: "yaml", + + value: rawAuthgearYAML ?? "", + onChange, + }, + ], + }, + ]; + return ( - <> + - + ); }; diff --git a/portal/src/resources.ts b/portal/src/resources.ts index f1203565a3..4030b067a8 100644 --- a/portal/src/resources.ts +++ b/portal/src/resources.ts @@ -259,6 +259,13 @@ export const RESOURCE_USERNAME_EXCLUDED_KEYWORDS_TXT: ResourceDefinition = { optional: true, }; +export const RESOURCE_AUTHGEAR_YAML: ResourceDefinition = { + resourcePath: resourcePath`authgear.yaml`, + type: "text", + extensions: [], + optional: false, +}; + export const TRANSLATION_JSON_KEY_EMAIL_FORGOT_PASSWORD_LINK_SUBJECT = "email.forgot-password.subject"; export const TRANSLATION_JSON_KEY_EMAIL_FORGOT_PASSWORD_CODE_SUBJECT = From 497231613e0c6641872bba90d7dcb8eb52608e8d Mon Sep 17 00:00:00 2001 From: pkong-ds Date: Tue, 20 Aug 2024 12:37:05 +0800 Subject: [PATCH 8/8] Refetch app config on save in `/edit-config` screen --- .../portal/EditConfigurationScreen.tsx | 32 ++++--------------- 1 file changed, 7 insertions(+), 25 deletions(-) diff --git a/portal/src/graphql/portal/EditConfigurationScreen.tsx b/portal/src/graphql/portal/EditConfigurationScreen.tsx index a2d8697047..5d0df517d0 100644 --- a/portal/src/graphql/portal/EditConfigurationScreen.tsx +++ b/portal/src/graphql/portal/EditConfigurationScreen.tsx @@ -22,6 +22,7 @@ import { specifierId, } from "../../util/resource"; import { RESOURCE_AUTHGEAR_YAML } from "../../resources"; +import { useAppAndSecretConfigQuery } from "./query/appAndSecretConfigQuery"; interface FormModel { isLoading: boolean; @@ -47,36 +48,17 @@ const EditConfigurationScreen: React.VFC = function EditConfigurationScreen() { const { appID } = useParams() as { appID: string }; const specifiers = [AUTHGEAR_YAML_RESOURCE_SPECIFIER]; const resourceForm = useResourceForm(appID, specifiers); - - const state = useMemo(() => { - return { - resources: resourceForm.state.resources, - }; - }, [resourceForm.state.resources]); + const { refetch } = useAppAndSecretConfigQuery(appID); const form: FormModel = useMemo( () => ({ - isLoading: resourceForm.isLoading, - isUpdating: resourceForm.isUpdating, - isDirty: resourceForm.isDirty, - loadError: resourceForm.loadError, - updateError: resourceForm.updateError, - state, - setState: (fn) => { - const newState = fn(state); - resourceForm.setState(() => ({ resources: newState.resources })); - }, - reload: () => { - resourceForm.reload(); - }, - reset: () => { - resourceForm.reset(); - }, - save: async (ignoreConflict: boolean = false) => { - await resourceForm.save(ignoreConflict); + ...resourceForm, + save: async (...args: Parameters<(typeof resourceForm)["save"]>) => { + await resourceForm.save(...args); + await refetch(); }, }), - [resourceForm, state] + [refetch, resourceForm] ); const rawAuthgearYAML = useMemo(() => {