@carlonicora/nextjs-jsonapi 1.24.2 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (116) hide show
  1. package/dist/{BlockNoteEditor-7OSPCSFW.js → BlockNoteEditor-CKMTHP7C.js} +13 -13
  2. package/dist/{BlockNoteEditor-7OSPCSFW.js.map → BlockNoteEditor-CKMTHP7C.js.map} +1 -1
  3. package/dist/{BlockNoteEditor-63GKCJK3.mjs → BlockNoteEditor-EJQLNOLB.mjs} +3 -3
  4. package/dist/billing/index.js +345 -348
  5. package/dist/billing/index.js.map +1 -1
  6. package/dist/billing/index.mjs +6 -9
  7. package/dist/billing/index.mjs.map +1 -1
  8. package/dist/{chunk-UTPWUC6O.mjs → chunk-JNLXGGHE.mjs} +5790 -4519
  9. package/dist/chunk-JNLXGGHE.mjs.map +1 -0
  10. package/dist/{chunk-5U4NJJOF.mjs → chunk-LNBT2YPZ.mjs} +289 -2
  11. package/dist/chunk-LNBT2YPZ.mjs.map +1 -0
  12. package/dist/{chunk-NQVPCNRS.js → chunk-O3LLMGP7.js} +290 -3
  13. package/dist/chunk-O3LLMGP7.js.map +1 -0
  14. package/dist/{chunk-HIKTQMCR.js → chunk-YYZ2U4WU.js} +7332 -6061
  15. package/dist/chunk-YYZ2U4WU.js.map +1 -0
  16. package/dist/client/index.d.mts +96 -1
  17. package/dist/client/index.d.ts +96 -1
  18. package/dist/client/index.js +9 -3
  19. package/dist/client/index.js.map +1 -1
  20. package/dist/client/index.mjs +8 -2
  21. package/dist/components/index.d.mts +291 -32
  22. package/dist/components/index.d.ts +291 -32
  23. package/dist/components/index.js +43 -3
  24. package/dist/components/index.js.map +1 -1
  25. package/dist/components/index.mjs +58 -18
  26. package/dist/contexts/index.js +3 -3
  27. package/dist/contexts/index.mjs +2 -2
  28. package/dist/core/index.d.mts +108 -1
  29. package/dist/core/index.d.ts +108 -1
  30. package/dist/core/index.js +14 -2
  31. package/dist/core/index.js.map +1 -1
  32. package/dist/core/index.mjs +13 -1
  33. package/dist/index.d.mts +2 -1
  34. package/dist/index.d.ts +2 -1
  35. package/dist/index.js +14 -2
  36. package/dist/index.js.map +1 -1
  37. package/dist/index.mjs +13 -1
  38. package/dist/oauth.interface-DsZ5ecSX.d.mts +119 -0
  39. package/dist/oauth.interface-vL7za9Bz.d.ts +119 -0
  40. package/dist/scripts/generate-web-module/templates/components/editor.template.js +11 -13
  41. package/dist/scripts/generate-web-module/templates/components/editor.template.js.map +1 -1
  42. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.d.ts.map +1 -1
  43. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js +13 -26
  44. package/dist/scripts/generate-web-module/templates/components/multi-selector.template.js.map +1 -1
  45. package/dist/scripts/generate-web-module/templates/components/selector.template.d.ts.map +1 -1
  46. package/dist/scripts/generate-web-module/templates/components/selector.template.js +59 -76
  47. package/dist/scripts/generate-web-module/templates/components/selector.template.js.map +1 -1
  48. package/dist/scripts/generate-web-module/transformers/field-mapper.d.ts.map +1 -1
  49. package/dist/scripts/generate-web-module/transformers/field-mapper.js +10 -12
  50. package/dist/scripts/generate-web-module/transformers/field-mapper.js.map +1 -1
  51. package/dist/server/index.js +3 -3
  52. package/dist/server/index.mjs +1 -1
  53. package/package.json +1 -1
  54. package/scripts/generate-web-module/templates/components/editor.template.ts +11 -13
  55. package/scripts/generate-web-module/templates/components/multi-selector.template.ts +13 -26
  56. package/scripts/generate-web-module/templates/components/selector.template.ts +59 -76
  57. package/scripts/generate-web-module/transformers/field-mapper.ts +10 -12
  58. package/src/client/index.ts +1 -0
  59. package/src/components/forms/FormCheckbox.tsx +18 -24
  60. package/src/components/forms/FormDate.tsx +103 -116
  61. package/src/components/forms/FormDateTime.tsx +122 -130
  62. package/src/components/forms/FormFieldWrapper.tsx +54 -0
  63. package/src/components/forms/FormInput.tsx +58 -46
  64. package/src/components/forms/FormPassword.tsx +17 -24
  65. package/src/components/forms/FormPlaceAutocomplete.tsx +50 -75
  66. package/src/components/forms/FormSelect.tsx +29 -35
  67. package/src/components/forms/FormSlider.tsx +23 -27
  68. package/src/components/forms/FormSwitch.tsx +12 -14
  69. package/src/components/forms/FormTextarea.tsx +12 -19
  70. package/src/components/forms/index.ts +1 -1
  71. package/src/components/index.ts +1 -0
  72. package/src/core/index.ts +3 -0
  73. package/src/core/registry/ModuleRegistry.ts +2 -0
  74. package/src/features/billing/stripe-price/components/forms/PriceEditor.tsx +9 -13
  75. package/src/features/company/components/forms/CompanyConfigurationSecurityForm.tsx +19 -33
  76. package/src/features/feature/components/forms/FormFeatures.tsx +3 -4
  77. package/src/features/index.ts +1 -0
  78. package/src/features/oauth/atoms/index.ts +1 -0
  79. package/src/features/oauth/atoms/oauth.atoms.ts +131 -0
  80. package/src/features/oauth/components/OAuthClientCard.tsx +105 -0
  81. package/src/features/oauth/components/OAuthClientDetail.tsx +269 -0
  82. package/src/features/oauth/components/OAuthClientForm.tsx +212 -0
  83. package/src/features/oauth/components/OAuthClientList.tsx +127 -0
  84. package/src/features/oauth/components/OAuthClientSecretDisplay.tsx +127 -0
  85. package/src/features/oauth/components/OAuthRedirectUriInput.tsx +152 -0
  86. package/src/features/oauth/components/OAuthScopeSelector.tsx +123 -0
  87. package/src/features/oauth/components/consent/OAuthConsentActions.tsx +41 -0
  88. package/src/features/oauth/components/consent/OAuthConsentHeader.tsx +51 -0
  89. package/src/features/oauth/components/consent/OAuthConsentScreen.tsx +142 -0
  90. package/src/features/oauth/components/consent/OAuthScopeList.tsx +72 -0
  91. package/src/features/oauth/components/consent/index.ts +4 -0
  92. package/src/features/oauth/components/index.ts +8 -0
  93. package/src/features/oauth/data/index.ts +2 -0
  94. package/src/features/oauth/data/oauth.service.ts +191 -0
  95. package/src/features/oauth/data/oauth.ts +87 -0
  96. package/src/features/oauth/hooks/index.ts +3 -0
  97. package/src/features/oauth/hooks/useOAuthClient.ts +161 -0
  98. package/src/features/oauth/hooks/useOAuthClients.ts +111 -0
  99. package/src/features/oauth/hooks/useOAuthConsent.ts +125 -0
  100. package/src/features/oauth/index.ts +6 -0
  101. package/src/features/oauth/interfaces/index.ts +1 -0
  102. package/src/features/oauth/interfaces/oauth.interface.ts +175 -0
  103. package/src/features/oauth/oauth.module.ts +9 -0
  104. package/src/features/role/components/forms/FormRoles.tsx +40 -51
  105. package/src/features/user/components/forms/UserMultiSelect.tsx +12 -29
  106. package/src/features/user/components/forms/UserSelector.tsx +79 -91
  107. package/src/shadcnui/index.ts +2 -0
  108. package/src/shadcnui/ui/field.tsx +3 -3
  109. package/src/shadcnui/ui/form.tsx +17 -134
  110. package/src/shadcnui/ui/input-group.tsx +4 -4
  111. package/dist/chunk-5U4NJJOF.mjs.map +0 -1
  112. package/dist/chunk-HIKTQMCR.js.map +0 -1
  113. package/dist/chunk-NQVPCNRS.js.map +0 -1
  114. package/dist/chunk-UTPWUC6O.mjs.map +0 -1
  115. package/src/components/forms/FormContainerGeneric.tsx +0 -39
  116. /package/dist/{BlockNoteEditor-63GKCJK3.mjs.map → BlockNoteEditor-EJQLNOLB.mjs.map} +0 -0
@@ -0,0 +1,87 @@
1
+ import { AbstractApiData, JsonApiHydratedDataInterface } from "../../../core";
2
+ import { OAuthClientInput, OAuthClientInterface } from "../interfaces/oauth.interface";
3
+
4
+ /**
5
+ * OAuth client data model
6
+ * Represents a registered OAuth application that can request access tokens
7
+ */
8
+ export class OAuthClient extends AbstractApiData implements OAuthClientInterface {
9
+ private _clientId?: string;
10
+ private _name?: string;
11
+ private _description?: string;
12
+ private _redirectUris: string[] = [];
13
+ private _allowedScopes: string[] = [];
14
+ private _allowedGrantTypes: string[] = [];
15
+ private _isConfidential: boolean = true;
16
+ private _isActive: boolean = true;
17
+
18
+ get clientId(): string {
19
+ return this._clientId ?? this.id;
20
+ }
21
+
22
+ get name(): string {
23
+ if (!this._name) throw new Error("Name is not defined");
24
+ return this._name;
25
+ }
26
+
27
+ get description(): string | undefined {
28
+ return this._description;
29
+ }
30
+
31
+ get redirectUris(): string[] {
32
+ return this._redirectUris;
33
+ }
34
+
35
+ get allowedScopes(): string[] {
36
+ return this._allowedScopes;
37
+ }
38
+
39
+ get allowedGrantTypes(): string[] {
40
+ return this._allowedGrantTypes;
41
+ }
42
+
43
+ get isConfidential(): boolean {
44
+ return this._isConfidential;
45
+ }
46
+
47
+ get isActive(): boolean {
48
+ return this._isActive;
49
+ }
50
+
51
+ rehydrate(data: JsonApiHydratedDataInterface): this {
52
+ super.rehydrate(data);
53
+
54
+ const attrs = data.jsonApi.attributes || {};
55
+
56
+ this._clientId = attrs.clientId ?? this._id;
57
+ this._name = attrs.name;
58
+ this._description = attrs.description;
59
+ this._redirectUris = attrs.redirectUris ?? [];
60
+ this._allowedScopes = attrs.allowedScopes ?? [];
61
+ this._allowedGrantTypes = attrs.allowedGrantTypes ?? [];
62
+ this._isConfidential = attrs.isConfidential ?? true;
63
+ this._isActive = attrs.isActive ?? true;
64
+
65
+ return this;
66
+ }
67
+
68
+ createJsonApi(data: OAuthClientInput) {
69
+ const response: any = {
70
+ data: {
71
+ type: "oauth-clients",
72
+ attributes: {},
73
+ },
74
+ };
75
+
76
+ if (data.id) response.data.id = data.id;
77
+ if (data.name !== undefined) response.data.attributes.name = data.name;
78
+ if (data.description !== undefined) response.data.attributes.description = data.description;
79
+ if (data.redirectUris !== undefined) response.data.attributes.redirectUris = data.redirectUris;
80
+ if (data.allowedScopes !== undefined) response.data.attributes.allowedScopes = data.allowedScopes;
81
+ if (data.allowedGrantTypes !== undefined) response.data.attributes.allowedGrantTypes = data.allowedGrantTypes;
82
+ if (data.isConfidential !== undefined) response.data.attributes.isConfidential = data.isConfidential;
83
+ if (data.isActive !== undefined) response.data.attributes.isActive = data.isActive;
84
+
85
+ return response;
86
+ }
87
+ }
@@ -0,0 +1,3 @@
1
+ export * from "./useOAuthClients";
2
+ export * from "./useOAuthClient";
3
+ export * from "./useOAuthConsent";
@@ -0,0 +1,161 @@
1
+ "use client";
2
+
3
+ import { useAtomValue, useSetAtom } from "jotai";
4
+ import { useCallback, useEffect, useState } from "react";
5
+ import {
6
+ oauthClientByIdAtom,
7
+ removeOAuthClientAtom,
8
+ setNewClientSecretAtom,
9
+ updateOAuthClientAtom,
10
+ } from "../atoms/oauth.atoms";
11
+ import { OAuthClientInput, OAuthClientInterface } from "../interfaces/oauth.interface";
12
+ import { OAuthService } from "../data/oauth.service";
13
+
14
+ export interface UseOAuthClientReturn {
15
+ /** The OAuth client (from store or fetched) */
16
+ client: OAuthClientInterface | null;
17
+ /** Whether the client is being loaded */
18
+ isLoading: boolean;
19
+ /** Error from last operation */
20
+ error: Error | null;
21
+ /** Update the client */
22
+ update: (data: Partial<OAuthClientInput>) => Promise<void>;
23
+ /** Delete the client */
24
+ deleteClient: () => Promise<void>;
25
+ /** Regenerate the client secret */
26
+ regenerateSecret: () => Promise<string>;
27
+ /** Refetch client from API */
28
+ refetch: () => Promise<void>;
29
+ }
30
+
31
+ /**
32
+ * Hook for managing a single OAuth client
33
+ *
34
+ * @param clientId - The client ID to manage
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * const { client, update, deleteClient, regenerateSecret } = useOAuthClient(clientId);
39
+ *
40
+ * const handleRegenerate = async () => {
41
+ * const newSecret = await regenerateSecret();
42
+ * // newSecret is shown only once!
43
+ * };
44
+ * ```
45
+ */
46
+ export function useOAuthClient(clientId: string): UseOAuthClientReturn {
47
+ // Try to get from store first (populated by useOAuthClients)
48
+ const storedClient = useAtomValue(oauthClientByIdAtom(clientId));
49
+ const updateClientInStore = useSetAtom(updateOAuthClientAtom);
50
+ const removeClientFromStore = useSetAtom(removeOAuthClientAtom);
51
+ const setNewClientSecret = useSetAtom(setNewClientSecretAtom);
52
+
53
+ // Local state for fetched client (if not in store)
54
+ const [fetchedClient, setFetchedClient] = useState<OAuthClientInterface | null>(null);
55
+ const [isLoading, setIsLoading] = useState(false);
56
+ const [error, setError] = useState<Error | null>(null);
57
+
58
+ const client = storedClient || fetchedClient;
59
+
60
+ const fetchClient = useCallback(async () => {
61
+ if (!clientId) return;
62
+
63
+ setIsLoading(true);
64
+ setError(null);
65
+
66
+ try {
67
+ const fetched = await OAuthService.getClient({ clientId });
68
+ setFetchedClient(fetched);
69
+ } catch (err) {
70
+ console.error("[useOAuthClient] Failed to fetch client:", err);
71
+ setError(err instanceof Error ? err : new Error("Failed to fetch OAuth client"));
72
+ } finally {
73
+ setIsLoading(false);
74
+ }
75
+ }, [clientId]);
76
+
77
+ // Fetch if not in store
78
+ useEffect(() => {
79
+ if (!storedClient && clientId) {
80
+ fetchClient();
81
+ }
82
+ }, [storedClient, clientId, fetchClient]);
83
+
84
+ const update = useCallback(
85
+ async (data: Partial<OAuthClientInput>): Promise<void> => {
86
+ if (!clientId) throw new Error("No client ID");
87
+
88
+ setIsLoading(true);
89
+ setError(null);
90
+
91
+ try {
92
+ const updated = await OAuthService.updateClient({ clientId, data });
93
+ updateClientInStore(updated);
94
+ setFetchedClient(updated);
95
+ } catch (err) {
96
+ console.error("[useOAuthClient] Failed to update client:", err);
97
+ const error = err instanceof Error ? err : new Error("Failed to update OAuth client");
98
+ setError(error);
99
+ throw error;
100
+ } finally {
101
+ setIsLoading(false);
102
+ }
103
+ },
104
+ [clientId, updateClientInStore],
105
+ );
106
+
107
+ const deleteClient = useCallback(async (): Promise<void> => {
108
+ if (!clientId) throw new Error("No client ID");
109
+
110
+ setIsLoading(true);
111
+ setError(null);
112
+
113
+ try {
114
+ await OAuthService.deleteClient({ clientId });
115
+ removeClientFromStore(clientId);
116
+ } catch (err) {
117
+ console.error("[useOAuthClient] Failed to delete client:", err);
118
+ const error = err instanceof Error ? err : new Error("Failed to delete OAuth client");
119
+ setError(error);
120
+ throw error;
121
+ } finally {
122
+ setIsLoading(false);
123
+ }
124
+ }, [clientId, removeClientFromStore]);
125
+
126
+ const regenerateSecret = useCallback(async (): Promise<string> => {
127
+ if (!clientId) throw new Error("No client ID");
128
+
129
+ setIsLoading(true);
130
+ setError(null);
131
+
132
+ try {
133
+ const result = await OAuthService.regenerateSecret({ clientId });
134
+
135
+ // Store for one-time display
136
+ setNewClientSecret({
137
+ clientId,
138
+ secret: result.clientSecret,
139
+ });
140
+
141
+ return result.clientSecret;
142
+ } catch (err) {
143
+ console.error("[useOAuthClient] Failed to regenerate secret:", err);
144
+ const error = err instanceof Error ? err : new Error("Failed to regenerate client secret");
145
+ setError(error);
146
+ throw error;
147
+ } finally {
148
+ setIsLoading(false);
149
+ }
150
+ }, [clientId, setNewClientSecret]);
151
+
152
+ return {
153
+ client,
154
+ isLoading,
155
+ error,
156
+ update,
157
+ deleteClient,
158
+ regenerateSecret,
159
+ refetch: fetchClient,
160
+ };
161
+ }
@@ -0,0 +1,111 @@
1
+ "use client";
2
+
3
+ import { useAtom, useSetAtom } from "jotai";
4
+ import { useCallback, useEffect } from "react";
5
+ import {
6
+ addOAuthClientAtom,
7
+ oauthClientsAtom,
8
+ oauthClientsErrorAtom,
9
+ oauthClientsLoadingAtom,
10
+ setNewClientSecretAtom,
11
+ } from "../atoms/oauth.atoms";
12
+ import {
13
+ OAuthClientCreateRequest,
14
+ OAuthClientCreateResponse,
15
+ OAuthClientInterface,
16
+ } from "../interfaces/oauth.interface";
17
+ import { OAuthService } from "../data/oauth.service";
18
+
19
+ export interface UseOAuthClientsReturn {
20
+ /** List of OAuth clients */
21
+ clients: OAuthClientInterface[];
22
+ /** Whether clients are being loaded */
23
+ isLoading: boolean;
24
+ /** Error from last operation */
25
+ error: Error | null;
26
+ /** Refetch clients from API */
27
+ refetch: () => Promise<void>;
28
+ /** Create a new OAuth client */
29
+ createClient: (data: OAuthClientCreateRequest) => Promise<OAuthClientCreateResponse>;
30
+ }
31
+
32
+ /**
33
+ * Hook for managing OAuth clients list
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * const { clients, isLoading, createClient } = useOAuthClients();
38
+ *
39
+ * const handleCreate = async (data) => {
40
+ * const { client, clientSecret } = await createClient(data);
41
+ * // clientSecret is shown only once!
42
+ * };
43
+ * ```
44
+ */
45
+ export function useOAuthClients(): UseOAuthClientsReturn {
46
+ const [clients, setClients] = useAtom(oauthClientsAtom);
47
+ const [isLoading, setIsLoading] = useAtom(oauthClientsLoadingAtom);
48
+ const [error, setError] = useAtom(oauthClientsErrorAtom);
49
+ const addClient = useSetAtom(addOAuthClientAtom);
50
+ const setNewClientSecret = useSetAtom(setNewClientSecretAtom);
51
+
52
+ const fetchClients = useCallback(async () => {
53
+ setIsLoading(true);
54
+ setError(null);
55
+
56
+ try {
57
+ const fetchedClients = await OAuthService.listClients();
58
+ setClients(fetchedClients);
59
+ } catch (err) {
60
+ console.error("[useOAuthClients] Failed to fetch clients:", err);
61
+ setError(err instanceof Error ? err : new Error("Failed to fetch OAuth clients"));
62
+ } finally {
63
+ setIsLoading(false);
64
+ }
65
+ }, [setClients, setIsLoading, setError]);
66
+
67
+ // Fetch clients on mount
68
+ useEffect(() => {
69
+ fetchClients();
70
+ }, [fetchClients]);
71
+
72
+ const createClient = useCallback(
73
+ async (data: OAuthClientCreateRequest): Promise<OAuthClientCreateResponse> => {
74
+ setIsLoading(true);
75
+ setError(null);
76
+
77
+ try {
78
+ const result = await OAuthService.createClient(data);
79
+
80
+ // Add to local state
81
+ addClient(result.client);
82
+
83
+ // Store secret for one-time display
84
+ if (result.clientSecret) {
85
+ setNewClientSecret({
86
+ clientId: result.client.clientId,
87
+ secret: result.clientSecret,
88
+ });
89
+ }
90
+
91
+ return result;
92
+ } catch (err) {
93
+ console.error("[useOAuthClients] Failed to create client:", err);
94
+ const error = err instanceof Error ? err : new Error("Failed to create OAuth client");
95
+ setError(error);
96
+ throw error;
97
+ } finally {
98
+ setIsLoading(false);
99
+ }
100
+ },
101
+ [addClient, setNewClientSecret, setIsLoading, setError],
102
+ );
103
+
104
+ return {
105
+ clients,
106
+ isLoading,
107
+ error,
108
+ refetch: fetchClients,
109
+ createClient,
110
+ };
111
+ }
@@ -0,0 +1,125 @@
1
+ "use client";
2
+
3
+ import { useCallback, useEffect, useState } from "react";
4
+ import { OAuthConsentInfo, OAuthConsentRequest } from "../interfaces/oauth.interface";
5
+ import { OAuthService } from "../data/oauth.service";
6
+
7
+ export interface UseOAuthConsentReturn {
8
+ /** Client and scope info for consent display */
9
+ clientInfo: OAuthConsentInfo | null;
10
+ /** Whether consent info is being loaded */
11
+ isLoading: boolean;
12
+ /** Error from consent flow */
13
+ error: Error | null;
14
+ /** Approve the authorization request */
15
+ approve: () => Promise<void>;
16
+ /** Deny the authorization request */
17
+ deny: () => Promise<void>;
18
+ /** Whether approve/deny is in progress */
19
+ isSubmitting: boolean;
20
+ }
21
+
22
+ /**
23
+ * Hook for managing the OAuth consent flow
24
+ *
25
+ * @param params - OAuth authorization parameters from URL
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * const { clientInfo, isLoading, approve, deny } = useOAuthConsent({
30
+ * clientId: searchParams.client_id,
31
+ * redirectUri: searchParams.redirect_uri,
32
+ * scope: searchParams.scope,
33
+ * state: searchParams.state,
34
+ * });
35
+ *
36
+ * // Render consent screen with clientInfo
37
+ * // On button click: approve() or deny()
38
+ * ```
39
+ */
40
+ export function useOAuthConsent(params: OAuthConsentRequest): UseOAuthConsentReturn {
41
+ const [clientInfo, setClientInfo] = useState<OAuthConsentInfo | null>(null);
42
+ const [isLoading, setIsLoading] = useState(true);
43
+ const [error, setError] = useState<Error | null>(null);
44
+ const [isSubmitting, setIsSubmitting] = useState(false);
45
+
46
+ // Fetch client info on mount
47
+ useEffect(() => {
48
+ const fetchInfo = async () => {
49
+ if (!params.clientId || !params.redirectUri || !params.scope) {
50
+ setError(new Error("Missing required authorization parameters"));
51
+ setIsLoading(false);
52
+ return;
53
+ }
54
+
55
+ setIsLoading(true);
56
+ setError(null);
57
+
58
+ try {
59
+ const info = await OAuthService.getAuthorizationInfo(params);
60
+ setClientInfo(info);
61
+ } catch (err) {
62
+ console.error("[useOAuthConsent] Failed to fetch authorization info:", err);
63
+ setError(err instanceof Error ? err : new Error("Failed to load authorization info"));
64
+ } finally {
65
+ setIsLoading(false);
66
+ }
67
+ };
68
+
69
+ fetchInfo();
70
+ }, [
71
+ params.clientId,
72
+ params.redirectUri,
73
+ params.scope,
74
+ params.state,
75
+ params.codeChallenge,
76
+ params.codeChallengeMethod,
77
+ ]);
78
+
79
+ const approve = useCallback(async (): Promise<void> => {
80
+ setIsSubmitting(true);
81
+ setError(null);
82
+
83
+ try {
84
+ const result = await OAuthService.approveAuthorization(params);
85
+
86
+ // Redirect to client with authorization code
87
+ if (result.redirectUrl) {
88
+ window.location.href = result.redirectUrl;
89
+ }
90
+ } catch (err) {
91
+ console.error("[useOAuthConsent] Failed to approve authorization:", err);
92
+ setError(err instanceof Error ? err : new Error("Failed to approve authorization"));
93
+ setIsSubmitting(false);
94
+ }
95
+ // Note: Don't set isSubmitting to false on success - we're redirecting
96
+ }, [params]);
97
+
98
+ const deny = useCallback(async (): Promise<void> => {
99
+ setIsSubmitting(true);
100
+ setError(null);
101
+
102
+ try {
103
+ const result = await OAuthService.denyAuthorization(params);
104
+
105
+ // Redirect to client with error
106
+ if (result.redirectUrl) {
107
+ window.location.href = result.redirectUrl;
108
+ }
109
+ } catch (err) {
110
+ console.error("[useOAuthConsent] Failed to deny authorization:", err);
111
+ setError(err instanceof Error ? err : new Error("Failed to deny authorization"));
112
+ setIsSubmitting(false);
113
+ }
114
+ // Note: Don't set isSubmitting to false on success - we're redirecting
115
+ }, [params]);
116
+
117
+ return {
118
+ clientInfo,
119
+ isLoading,
120
+ error,
121
+ approve,
122
+ deny,
123
+ isSubmitting,
124
+ };
125
+ }
@@ -0,0 +1,6 @@
1
+ export * from "./oauth.module";
2
+ export * from "./interfaces";
3
+ export * from "./data";
4
+ export * from "./atoms";
5
+ export * from "./hooks";
6
+ export * from "./components";
@@ -0,0 +1 @@
1
+ export * from "./oauth.interface";
@@ -0,0 +1,175 @@
1
+ import { ApiDataInterface } from "../../../core";
2
+
3
+ /**
4
+ * OAuth client application interface
5
+ * Represents a registered OAuth application that can request access tokens
6
+ */
7
+ export interface OAuthClientInterface extends ApiDataInterface {
8
+ /** The public client identifier (UUID format) */
9
+ get clientId(): string;
10
+ /** Human-readable application name */
11
+ get name(): string;
12
+ /** Optional description of the application */
13
+ get description(): string | undefined;
14
+ /** Array of allowed redirect URIs (exact match validation) */
15
+ get redirectUris(): string[];
16
+ /** Array of scopes this client can request */
17
+ get allowedScopes(): string[];
18
+ /** Supported grant types (authorization_code, client_credentials, refresh_token) */
19
+ get allowedGrantTypes(): string[];
20
+ /** True for server-side apps (can keep secret secure), false for mobile/desktop apps */
21
+ get isConfidential(): boolean;
22
+ /** Whether the client is currently active */
23
+ get isActive(): boolean;
24
+ /** When the client was created */
25
+ get createdAt(): Date;
26
+ /** When the client was last updated */
27
+ get updatedAt(): Date;
28
+ }
29
+
30
+ /**
31
+ * Input type for OAuth client CRUD operations
32
+ */
33
+ export type OAuthClientInput = {
34
+ id?: string;
35
+ name?: string;
36
+ description?: string;
37
+ redirectUris?: string[];
38
+ allowedScopes?: string[];
39
+ allowedGrantTypes?: string[];
40
+ isConfidential?: boolean;
41
+ isActive?: boolean;
42
+ };
43
+
44
+ /**
45
+ * Request body for creating a new OAuth client
46
+ */
47
+ export interface OAuthClientCreateRequest {
48
+ /** Required: Human-readable application name */
49
+ name: string;
50
+ /** Optional: Description of the application */
51
+ description?: string;
52
+ /** Required: At least one redirect URI */
53
+ redirectUris: string[];
54
+ /** Required: Array of scopes the client needs */
55
+ allowedScopes: string[];
56
+ /** Optional: Grant types (defaults to authorization_code + refresh_token) */
57
+ allowedGrantTypes?: string[];
58
+ /** Required: Whether this is a confidential client */
59
+ isConfidential: boolean;
60
+ }
61
+
62
+ /**
63
+ * Response when creating a client (includes one-time secret)
64
+ */
65
+ export interface OAuthClientCreateResponse {
66
+ client: OAuthClientInterface;
67
+ /** Only returned on creation - must be saved immediately */
68
+ clientSecret?: string;
69
+ }
70
+
71
+ /**
72
+ * Parameters for the OAuth authorization consent flow
73
+ * Passed via URL query parameters to the consent page
74
+ */
75
+ export interface OAuthConsentRequest {
76
+ /** The client_id requesting authorization */
77
+ clientId: string;
78
+ /** Where to redirect after authorization */
79
+ redirectUri: string;
80
+ /** Space-separated list of requested scopes */
81
+ scope: string;
82
+ /** CSRF protection token (passed back on redirect) */
83
+ state?: string;
84
+ /** PKCE code challenge (required for public clients) */
85
+ codeChallenge?: string;
86
+ /** PKCE method: 'S256' (recommended) or 'plain' */
87
+ codeChallengeMethod?: string;
88
+ }
89
+
90
+ /**
91
+ * Scope information for display in consent screen
92
+ */
93
+ export interface OAuthScopeInfo {
94
+ /** The scope identifier (e.g., 'photographs:read') */
95
+ scope: string;
96
+ /** Human-readable scope name */
97
+ name: string;
98
+ /** Description of what this scope allows */
99
+ description: string;
100
+ /** Optional icon identifier */
101
+ icon?: string;
102
+ }
103
+
104
+ /**
105
+ * Client info returned for consent screen display
106
+ */
107
+ export interface OAuthConsentInfo {
108
+ client: OAuthClientInterface;
109
+ scopes: OAuthScopeInfo[];
110
+ }
111
+
112
+ /**
113
+ * Default scope display configuration
114
+ * Maps scope identifiers to human-readable info
115
+ */
116
+ export const OAUTH_SCOPE_DISPLAY: Record<string, OAuthScopeInfo> = {
117
+ read: {
118
+ scope: "read",
119
+ name: "Read Access",
120
+ description: "Read access to your data",
121
+ icon: "eye",
122
+ },
123
+ write: {
124
+ scope: "write",
125
+ name: "Write Access",
126
+ description: "Write access to your data",
127
+ icon: "pencil",
128
+ },
129
+ "photographs:read": {
130
+ scope: "photographs:read",
131
+ name: "View Photographs",
132
+ description: "Access and download your photo library",
133
+ icon: "image",
134
+ },
135
+ "photographs:write": {
136
+ scope: "photographs:write",
137
+ name: "Upload Photographs",
138
+ description: "Add new photos to your rolls",
139
+ icon: "upload",
140
+ },
141
+ "rolls:read": {
142
+ scope: "rolls:read",
143
+ name: "View Rolls",
144
+ description: "See your film rolls and collections",
145
+ icon: "film",
146
+ },
147
+ "rolls:write": {
148
+ scope: "rolls:write",
149
+ name: "Manage Rolls",
150
+ description: "Create and modify film rolls",
151
+ icon: "folder-plus",
152
+ },
153
+ profile: {
154
+ scope: "profile",
155
+ name: "View Profile",
156
+ description: "Access your name and email",
157
+ icon: "user",
158
+ },
159
+ admin: {
160
+ scope: "admin",
161
+ name: "Administrative Access",
162
+ description: "Full administrative access to your account",
163
+ icon: "shield",
164
+ },
165
+ };
166
+
167
+ /**
168
+ * Available scopes list for the scope selector
169
+ */
170
+ export const AVAILABLE_OAUTH_SCOPES: OAuthScopeInfo[] = Object.values(OAUTH_SCOPE_DISPLAY);
171
+
172
+ /**
173
+ * Default grant types for new clients
174
+ */
175
+ export const DEFAULT_GRANT_TYPES = ["authorization_code", "refresh_token"];
@@ -0,0 +1,9 @@
1
+ import { ModuleFactory } from "../../permissions";
2
+ import { OAuthClient } from "./data/oauth";
3
+
4
+ export const OAuthModule = (factory: ModuleFactory) =>
5
+ factory({
6
+ pageUrl: "/oauth",
7
+ name: "oauth-clients",
8
+ model: OAuthClient,
9
+ });