From d5847e2d2b68a1eb41d43be170cd4ddff9003cff Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Mon, 17 Mar 2025 15:06:17 +0100 Subject: [feature] Application creation + management via API + settings panel (#3906) * [feature] Application creation + management via API + settings panel * fix docs links * add errnorows test * use known application as shorter * add comment about side effects --- web/source/settings/lib/util/formvalidators.ts | 71 +++++++++++++++++++++++++- web/source/settings/lib/util/index.ts | 2 +- 2 files changed, 71 insertions(+), 2 deletions(-) (limited to 'web/source/settings/lib/util') diff --git a/web/source/settings/lib/util/formvalidators.ts b/web/source/settings/lib/util/formvalidators.ts index 358db616c..4e0e4a6a8 100644 --- a/web/source/settings/lib/util/formvalidators.ts +++ b/web/source/settings/lib/util/formvalidators.ts @@ -18,6 +18,8 @@ */ import isValidDomain from "is-valid-domain"; +import { useCallback } from "react"; +import { ValidScopes, ValidTopLevelScopes } from "../types/scopes"; /** * Validate the "domain" field of a form. @@ -29,6 +31,11 @@ export function formDomainValidator(domain: string): string { return ""; } + // Allow localhost for testing. + if (domain === "localhost") { + return ""; + } + if (domain[domain.length-1] === ".") { return "invalid domain"; } @@ -63,5 +70,67 @@ export function urlValidator(urlStr: string): string { return `invalid protocol, must be http or https`; } - return formDomainValidator(url.host); + return formDomainValidator(url.hostname); +} + +export function useScopesValidator(): (_scopes: string[]) => string { + return useCallback((scopes) => { + return scopes. + map((scope) => validateScope(scope)). + flatMap((msg) => msg || []). + join(", "); + }, []); +} + +export function useScopeValidator(): (_scope: string) => string { + return useCallback((scope) => validateScope(scope), []); +} + +const validateScope = (scope: string) => { + if (!ValidScopes.includes(scope)) { + return scope + " is not a recognized scope"; + } + return ""; +}; + +export function useScopesPermittedBy(): (_hasScopes: string[], _wantScopes: string[]) => string { + return useCallback((hasScopes, wantsScopes) => { + return wantsScopes. + map((wanted) => scopePermittedByScopes(hasScopes, wanted)). + flatMap((msg) => msg || []). + join(", "); + }, []); } + +const scopePermittedByScopes = (hasScopes: string[], wanted: string) => { + if (hasScopes.some((hasScope) => scopePermittedByScope(hasScope, wanted) === "")) { + return ""; + } + return `scopes [${hasScopes}] do not permit ${wanted}`; +}; + +const scopePermittedByScope = (has: string, wanted: string) => { + if (has === wanted) { + // Exact match on either a + // top-level or granular scope. + return ""; + } + + // Ensure we have a + // known top-level scope. + switch (true) { + case (ValidTopLevelScopes.includes(has)): + // Check if top-level includes wanted, + // eg., have "admin", want "admin:read". + if (wanted.startsWith(has + ":")) { + return ""; + } else { + return `scope ${has} does not permit ${wanted}`; + } + + default: + // Unknown top-level scope, + // can't permit anything. + return `unrecognized scope ${has}`; + } +}; diff --git a/web/source/settings/lib/util/index.ts b/web/source/settings/lib/util/index.ts index 4c8a90626..8bcf5ab5d 100644 --- a/web/source/settings/lib/util/index.ts +++ b/web/source/settings/lib/util/index.ts @@ -30,7 +30,7 @@ export function UseOurInstanceAccount(account: AdminAccount): boolean { // Pull our own URL out of storage so we can // tell if account is our instance account. const ourDomain = useMemo(() => { - const instanceUrlStr = store.getState().oauth.instanceUrl; + const instanceUrlStr = store.getState().login.instanceUrl; if (!instanceUrlStr) { return ""; } -- cgit v1.2.3