@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,123 @@
1
+ "use client";
2
+
3
+ import { useCallback } from "react";
4
+ import { Checkbox, Label } from "../../../shadcnui";
5
+ import { AVAILABLE_OAUTH_SCOPES, OAuthScopeInfo } from "../interfaces/oauth.interface";
6
+
7
+ export interface OAuthScopeSelectorProps {
8
+ /** Currently selected scopes */
9
+ value: string[];
10
+ /** Called when selection changes */
11
+ onChange: (scopes: string[]) => void;
12
+ /** Available scopes to display (defaults to all) */
13
+ availableScopes?: OAuthScopeInfo[];
14
+ /** Whether selector is disabled */
15
+ disabled?: boolean;
16
+ /** Error message */
17
+ error?: string;
18
+ /** Label text */
19
+ label?: string;
20
+ }
21
+
22
+ /**
23
+ * Checkbox selector for OAuth scopes
24
+ *
25
+ * @example
26
+ * ```tsx
27
+ * const [scopes, setScopes] = useState<string[]>([]);
28
+ *
29
+ * <OAuthScopeSelector
30
+ * value={scopes}
31
+ * onChange={setScopes}
32
+ * error={errors.scopes}
33
+ * />
34
+ * ```
35
+ */
36
+ export function OAuthScopeSelector({
37
+ value,
38
+ onChange,
39
+ availableScopes = AVAILABLE_OAUTH_SCOPES,
40
+ disabled = false,
41
+ error,
42
+ label = "Allowed Scopes",
43
+ }: OAuthScopeSelectorProps) {
44
+ const handleToggle = useCallback(
45
+ (scope: string, checked: boolean) => {
46
+ if (checked) {
47
+ onChange([...value, scope]);
48
+ } else {
49
+ onChange(value.filter((s) => s !== scope));
50
+ }
51
+ },
52
+ [value, onChange]
53
+ );
54
+
55
+ // Group scopes by category (before the colon)
56
+ const groupedScopes = availableScopes.reduce((acc, scope) => {
57
+ const [category] = scope.scope.split(":");
58
+ const groupName = category === scope.scope ? "General" : category;
59
+
60
+ if (!acc[groupName]) {
61
+ acc[groupName] = [];
62
+ }
63
+ acc[groupName].push(scope);
64
+ return acc;
65
+ }, {} as Record<string, OAuthScopeInfo[]>);
66
+
67
+ return (
68
+ <div className="space-y-4">
69
+ <div>
70
+ <Label>{label} *</Label>
71
+ <p className="text-sm text-muted-foreground">
72
+ Select the permissions your application needs.
73
+ </p>
74
+ </div>
75
+
76
+ <div className="space-y-4">
77
+ {Object.entries(groupedScopes).map(([groupName, scopes]) => (
78
+ <div key={groupName} className="space-y-2">
79
+ <h4 className="text-sm font-medium capitalize">{groupName}</h4>
80
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-2 pl-2">
81
+ {scopes.map((scopeInfo) => {
82
+ const isChecked = value.includes(scopeInfo.scope);
83
+ const isAdmin = scopeInfo.scope === "admin";
84
+
85
+ return (
86
+ <div
87
+ key={scopeInfo.scope}
88
+ className={`flex items-start space-x-3 p-2 rounded-md border ${
89
+ isChecked ? "bg-primary/5 border-primary/20" : "border-transparent"
90
+ } ${isAdmin ? "bg-destructive/5" : ""}`}
91
+ >
92
+ <Checkbox
93
+ id={`scope-${scopeInfo.scope}`}
94
+ checked={isChecked}
95
+ onCheckedChange={(checked) =>
96
+ handleToggle(scopeInfo.scope, checked === true)
97
+ }
98
+ disabled={disabled}
99
+ />
100
+ <div className="flex-1">
101
+ <Label
102
+ htmlFor={`scope-${scopeInfo.scope}`}
103
+ className="text-sm font-medium cursor-pointer"
104
+ >
105
+ {scopeInfo.name}
106
+ {isAdmin && (
107
+ <span className="ml-2 text-xs text-destructive">(Dangerous)</span>
108
+ )}
109
+ </Label>
110
+ <p className="text-xs text-muted-foreground">{scopeInfo.description}</p>
111
+ </div>
112
+ </div>
113
+ );
114
+ })}
115
+ </div>
116
+ </div>
117
+ ))}
118
+ </div>
119
+
120
+ {error && <p className="text-sm text-destructive">{error}</p>}
121
+ </div>
122
+ );
123
+ }
@@ -0,0 +1,41 @@
1
+ "use client";
2
+
3
+ import { Button } from "../../../../shadcnui";
4
+
5
+ export interface OAuthConsentActionsProps {
6
+ /** Called when user clicks Authorize */
7
+ onApprove: () => void;
8
+ /** Called when user clicks Deny */
9
+ onDeny: () => void;
10
+ /** Whether an action is in progress */
11
+ isLoading?: boolean;
12
+ }
13
+
14
+ /**
15
+ * Action buttons for OAuth consent screen
16
+ */
17
+ export function OAuthConsentActions({
18
+ onApprove,
19
+ onDeny,
20
+ isLoading = false,
21
+ }: OAuthConsentActionsProps) {
22
+ return (
23
+ <div className="flex flex-col sm:flex-row gap-3">
24
+ <Button
25
+ variant="outline"
26
+ onClick={onDeny}
27
+ disabled={isLoading}
28
+ className="flex-1"
29
+ >
30
+ Deny
31
+ </Button>
32
+ <Button
33
+ onClick={onApprove}
34
+ disabled={isLoading}
35
+ className="flex-1"
36
+ >
37
+ {isLoading ? "Authorizing..." : "Authorize"}
38
+ </Button>
39
+ </div>
40
+ );
41
+ }
@@ -0,0 +1,51 @@
1
+ "use client";
2
+
3
+ import { Shield } from "lucide-react";
4
+ import { OAuthClientInterface } from "../../interfaces/oauth.interface";
5
+
6
+ export interface OAuthConsentHeaderProps {
7
+ /** The requesting OAuth client */
8
+ client: OAuthClientInterface;
9
+ /** Optional logo URL override */
10
+ logoUrl?: string;
11
+ /** Application name (e.g., "Only35") */
12
+ appName?: string;
13
+ }
14
+
15
+ /**
16
+ * Header component for OAuth consent screen
17
+ * Shows platform logo and requesting app information
18
+ */
19
+ export function OAuthConsentHeader({
20
+ client,
21
+ logoUrl,
22
+ appName = "Only35",
23
+ }: OAuthConsentHeaderProps) {
24
+ return (
25
+ <div className="text-center space-y-4">
26
+ {/* Platform Logo */}
27
+ <div className="flex justify-center">
28
+ {logoUrl ? (
29
+ <img
30
+ src={logoUrl}
31
+ alt={appName}
32
+ className="h-12 w-auto"
33
+ />
34
+ ) : (
35
+ <div className="h-12 w-12 rounded-full bg-primary flex items-center justify-center">
36
+ <Shield className="h-6 w-6 text-primary-foreground" />
37
+ </div>
38
+ )}
39
+ </div>
40
+
41
+ {/* Authorization Request */}
42
+ <div className="space-y-2">
43
+ <h1 className="text-2xl font-bold">Authorize {client.name}</h1>
44
+ <p className="text-muted-foreground">
45
+ <span className="font-medium text-foreground">{client.name}</span>
46
+ {" "}wants to access your {appName} account
47
+ </p>
48
+ </div>
49
+ </div>
50
+ );
51
+ }
@@ -0,0 +1,142 @@
1
+ "use client";
2
+
3
+ import { ExternalLink, AlertTriangle, Loader2 } from "lucide-react";
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardFooter,
8
+ Separator,
9
+ Alert,
10
+ AlertDescription,
11
+ } from "../../../../shadcnui";
12
+ import { OAuthConsentHeader } from "./OAuthConsentHeader";
13
+ import { OAuthScopeList } from "./OAuthScopeList";
14
+ import { OAuthConsentActions } from "./OAuthConsentActions";
15
+ import { useOAuthConsent } from "../../hooks/useOAuthConsent";
16
+ import { OAuthConsentRequest } from "../../interfaces/oauth.interface";
17
+
18
+ export interface OAuthConsentScreenProps {
19
+ /** OAuth authorization parameters */
20
+ params: OAuthConsentRequest;
21
+ /** Optional platform logo URL */
22
+ logoUrl?: string;
23
+ /** Platform name */
24
+ appName?: string;
25
+ /** Terms of Service URL */
26
+ termsUrl?: string;
27
+ /** Privacy Policy URL */
28
+ privacyUrl?: string;
29
+ }
30
+
31
+ /**
32
+ * Main OAuth consent screen component
33
+ * Displays client info, requested scopes, and approve/deny buttons
34
+ *
35
+ * @example
36
+ * ```tsx
37
+ * <OAuthConsentScreen
38
+ * params={{
39
+ * clientId: searchParams.client_id,
40
+ * redirectUri: searchParams.redirect_uri,
41
+ * scope: searchParams.scope,
42
+ * state: searchParams.state,
43
+ * }}
44
+ * />
45
+ * ```
46
+ */
47
+ export function OAuthConsentScreen({
48
+ params,
49
+ logoUrl,
50
+ appName = "Only35",
51
+ termsUrl = "/terms",
52
+ privacyUrl = "/privacy",
53
+ }: OAuthConsentScreenProps) {
54
+ const { clientInfo, isLoading, error, approve, deny, isSubmitting } = useOAuthConsent(params);
55
+
56
+ // Loading state
57
+ if (isLoading) {
58
+ return (
59
+ <div className="min-h-screen flex items-center justify-center p-4">
60
+ <Card className="w-full max-w-md">
61
+ <CardContent className="flex flex-col items-center justify-center py-12">
62
+ <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" />
63
+ <p className="mt-4 text-muted-foreground">Loading authorization request...</p>
64
+ </CardContent>
65
+ </Card>
66
+ </div>
67
+ );
68
+ }
69
+
70
+ // Error state
71
+ if (error || !clientInfo) {
72
+ return (
73
+ <div className="min-h-screen flex items-center justify-center p-4">
74
+ <Card className="w-full max-w-md">
75
+ <CardContent className="py-8">
76
+ <Alert variant="destructive">
77
+ <AlertTriangle className="h-4 w-4" />
78
+ <AlertDescription>
79
+ {error?.message || "Invalid authorization request. Please try again."}
80
+ </AlertDescription>
81
+ </Alert>
82
+ </CardContent>
83
+ </Card>
84
+ </div>
85
+ );
86
+ }
87
+
88
+ const { client, scopes } = clientInfo;
89
+
90
+ return (
91
+ <div className="min-h-screen flex items-center justify-center p-4 bg-muted/30">
92
+ <Card className="w-full max-w-md">
93
+ <CardContent className="pt-6 space-y-6">
94
+ {/* Header */}
95
+ <OAuthConsentHeader
96
+ client={client}
97
+ logoUrl={logoUrl}
98
+ appName={appName}
99
+ />
100
+
101
+ <Separator />
102
+
103
+ {/* Scopes */}
104
+ <OAuthScopeList scopes={scopes} />
105
+
106
+ <Separator />
107
+
108
+ {/* Redirect URI Notice */}
109
+ <div className="flex items-start gap-2 text-sm text-muted-foreground">
110
+ <ExternalLink className="h-4 w-4 mt-0.5 flex-shrink-0" />
111
+ <div>
112
+ <span>Authorizing will redirect you to:</span>
113
+ <p className="font-mono text-xs mt-1 break-all">{params.redirectUri}</p>
114
+ </div>
115
+ </div>
116
+
117
+ {/* Actions */}
118
+ <OAuthConsentActions
119
+ onApprove={approve}
120
+ onDeny={deny}
121
+ isLoading={isSubmitting}
122
+ />
123
+ </CardContent>
124
+
125
+ {/* Footer */}
126
+ <CardFooter className="justify-center">
127
+ <p className="text-xs text-center text-muted-foreground">
128
+ By authorizing, you agree to the app's{" "}
129
+ <a href={termsUrl} className="underline hover:text-foreground" target="_blank" rel="noopener">
130
+ Terms of Service
131
+ </a>
132
+ {" "}and{" "}
133
+ <a href={privacyUrl} className="underline hover:text-foreground" target="_blank" rel="noopener">
134
+ Privacy Policy
135
+ </a>
136
+ .
137
+ </p>
138
+ </CardFooter>
139
+ </Card>
140
+ </div>
141
+ );
142
+ }
@@ -0,0 +1,72 @@
1
+ "use client";
2
+
3
+ import {
4
+ Eye,
5
+ Pencil,
6
+ Image,
7
+ Upload,
8
+ Film,
9
+ FolderPlus,
10
+ User,
11
+ Shield,
12
+ LucideIcon,
13
+ } from "lucide-react";
14
+ import { OAuthScopeInfo } from "../../interfaces/oauth.interface";
15
+
16
+ export interface OAuthScopeListProps {
17
+ /** List of requested scopes */
18
+ scopes: OAuthScopeInfo[];
19
+ }
20
+
21
+ /** Map scope icons to Lucide components */
22
+ const SCOPE_ICONS: Record<string, LucideIcon> = {
23
+ eye: Eye,
24
+ pencil: Pencil,
25
+ image: Image,
26
+ upload: Upload,
27
+ film: Film,
28
+ "folder-plus": FolderPlus,
29
+ user: User,
30
+ shield: Shield,
31
+ };
32
+
33
+ /**
34
+ * List of requested OAuth scopes for consent display
35
+ */
36
+ export function OAuthScopeList({ scopes }: OAuthScopeListProps) {
37
+ if (scopes.length === 0) {
38
+ return null;
39
+ }
40
+
41
+ return (
42
+ <div className="space-y-3">
43
+ <h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide">
44
+ This will allow the application to:
45
+ </h2>
46
+ <ul className="space-y-3">
47
+ {scopes.map((scope) => {
48
+ const IconComponent = scope.icon ? SCOPE_ICONS[scope.icon] : Eye;
49
+
50
+ return (
51
+ <li
52
+ key={scope.scope}
53
+ className="flex items-start gap-3 p-3 rounded-lg bg-muted/50"
54
+ >
55
+ <div className="flex-shrink-0 mt-0.5">
56
+ <div className="h-8 w-8 rounded-full bg-primary/10 flex items-center justify-center">
57
+ {IconComponent && (
58
+ <IconComponent className="h-4 w-4 text-primary" />
59
+ )}
60
+ </div>
61
+ </div>
62
+ <div className="flex-1">
63
+ <p className="font-medium">{scope.name}</p>
64
+ <p className="text-sm text-muted-foreground">{scope.description}</p>
65
+ </div>
66
+ </li>
67
+ );
68
+ })}
69
+ </ul>
70
+ </div>
71
+ );
72
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./OAuthConsentHeader";
2
+ export * from "./OAuthScopeList";
3
+ export * from "./OAuthConsentActions";
4
+ export * from "./OAuthConsentScreen";
@@ -0,0 +1,8 @@
1
+ export * from "./OAuthRedirectUriInput";
2
+ export * from "./OAuthScopeSelector";
3
+ export * from "./OAuthClientSecretDisplay";
4
+ export * from "./OAuthClientCard";
5
+ export * from "./OAuthClientList";
6
+ export * from "./OAuthClientForm";
7
+ export * from "./OAuthClientDetail";
8
+ export * from "./consent";
@@ -0,0 +1,2 @@
1
+ export * from "./oauth";
2
+ export * from "./oauth.service";
@@ -0,0 +1,191 @@
1
+ import { AbstractService, EndpointCreator, HttpMethod, Modules, NextRef } from "../../../core";
2
+ import {
3
+ OAuthClientCreateRequest,
4
+ OAuthClientCreateResponse,
5
+ OAuthClientInput,
6
+ OAuthClientInterface,
7
+ OAuthConsentInfo,
8
+ OAuthConsentRequest,
9
+ } from "../interfaces/oauth.interface";
10
+
11
+ /**
12
+ * Service for OAuth client management and authorization consent flow.
13
+ *
14
+ * Client Management endpoints:
15
+ * - GET /oauth/clients - List all clients for current user
16
+ * - GET /oauth/clients/:clientId - Get single client
17
+ * - POST /oauth/clients - Create new client (returns secret once)
18
+ * - PATCH /oauth/clients/:clientId - Update client
19
+ * - DELETE /oauth/clients/:clientId - Delete client
20
+ * - POST /oauth/clients/:clientId/regenerate-secret - Regenerate client secret
21
+ *
22
+ * Consent Flow endpoints:
23
+ * - GET /oauth/authorize/info - Get client info for consent screen
24
+ * - POST /oauth/authorize/approve - Approve authorization
25
+ * - POST /oauth/authorize/deny - Deny authorization
26
+ */
27
+ export class OAuthService extends AbstractService {
28
+ // ==========================================
29
+ // CLIENT MANAGEMENT
30
+ // ==========================================
31
+
32
+ /**
33
+ * List all OAuth clients for the current user
34
+ */
35
+ static async listClients(params?: { next?: NextRef }): Promise<OAuthClientInterface[]> {
36
+ return this.callApi<OAuthClientInterface[]>({
37
+ type: Modules.OAuth,
38
+ method: HttpMethod.GET,
39
+ endpoint: new EndpointCreator({ endpoint: "oauth/clients" }).generate(),
40
+ next: params?.next,
41
+ });
42
+ }
43
+
44
+ /**
45
+ * Get a single OAuth client by ID
46
+ */
47
+ static async getClient(params: { clientId: string }): Promise<OAuthClientInterface> {
48
+ return this.callApi<OAuthClientInterface>({
49
+ type: Modules.OAuth,
50
+ method: HttpMethod.GET,
51
+ endpoint: new EndpointCreator({ endpoint: "oauth/clients", id: params.clientId }).generate(),
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Create a new OAuth client
57
+ * @returns The created client AND the client secret (shown only once!)
58
+ */
59
+ static async createClient(data: OAuthClientCreateRequest): Promise<OAuthClientCreateResponse> {
60
+ const result = await this.callApiWithMeta<OAuthClientInterface>({
61
+ type: Modules.OAuth,
62
+ method: HttpMethod.POST,
63
+ endpoint: new EndpointCreator({ endpoint: "oauth/clients" }).generate(),
64
+ input: data,
65
+ });
66
+
67
+ return {
68
+ client: result.data,
69
+ clientSecret: result.meta?.clientSecret as string | undefined,
70
+ };
71
+ }
72
+
73
+ /**
74
+ * Update an existing OAuth client
75
+ */
76
+ static async updateClient(params: {
77
+ clientId: string;
78
+ data: Partial<OAuthClientInput>;
79
+ }): Promise<OAuthClientInterface> {
80
+ return this.callApi<OAuthClientInterface>({
81
+ type: Modules.OAuth,
82
+ method: HttpMethod.PATCH,
83
+ endpoint: new EndpointCreator({ endpoint: "oauth/clients", id: params.clientId }).generate(),
84
+ input: { id: params.clientId, ...params.data },
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Delete an OAuth client
90
+ */
91
+ static async deleteClient(params: { clientId: string }): Promise<void> {
92
+ await this.callApi({
93
+ type: Modules.OAuth,
94
+ method: HttpMethod.DELETE,
95
+ endpoint: new EndpointCreator({ endpoint: "oauth/clients", id: params.clientId }).generate(),
96
+ });
97
+ }
98
+
99
+ /**
100
+ * Regenerate the client secret
101
+ * @returns The new client secret (shown only once!)
102
+ */
103
+ static async regenerateSecret(params: { clientId: string }): Promise<{ clientSecret: string }> {
104
+ const result = await this.callApiWithMeta<OAuthClientInterface>({
105
+ type: Modules.OAuth,
106
+ method: HttpMethod.POST,
107
+ endpoint: new EndpointCreator({
108
+ endpoint: "oauth/clients",
109
+ id: params.clientId,
110
+ childEndpoint: "regenerate-secret",
111
+ }).generate(),
112
+ });
113
+
114
+ return {
115
+ clientSecret: result.meta?.clientSecret as string,
116
+ };
117
+ }
118
+
119
+ // ==========================================
120
+ // CONSENT FLOW
121
+ // ==========================================
122
+
123
+ /**
124
+ * Get client information for the consent screen
125
+ * Called when user is redirected to /oauth/authorize
126
+ */
127
+ static async getAuthorizationInfo(params: OAuthConsentRequest): Promise<OAuthConsentInfo> {
128
+ const endpoint = new EndpointCreator({ endpoint: "oauth/authorize/info" });
129
+
130
+ // Add query parameters
131
+ endpoint.addAdditionalParam("client_id", params.clientId);
132
+ endpoint.addAdditionalParam("redirect_uri", params.redirectUri);
133
+ endpoint.addAdditionalParam("scope", params.scope);
134
+ if (params.state) endpoint.addAdditionalParam("state", params.state);
135
+ if (params.codeChallenge) endpoint.addAdditionalParam("code_challenge", params.codeChallenge);
136
+ if (params.codeChallengeMethod) endpoint.addAdditionalParam("code_challenge_method", params.codeChallengeMethod);
137
+
138
+ return this.callApi<OAuthConsentInfo>({
139
+ type: Modules.OAuth,
140
+ method: HttpMethod.GET,
141
+ endpoint: endpoint.generate(),
142
+ });
143
+ }
144
+
145
+ /**
146
+ * Approve the authorization request
147
+ * @returns Redirect URL with authorization code
148
+ */
149
+ static async approveAuthorization(params: OAuthConsentRequest): Promise<{ redirectUrl: string }> {
150
+ const result = await this.callApiWithMeta<unknown>({
151
+ type: Modules.OAuth,
152
+ method: HttpMethod.POST,
153
+ endpoint: new EndpointCreator({ endpoint: "oauth/authorize/approve" }).generate(),
154
+ input: {
155
+ client_id: params.clientId,
156
+ redirect_uri: params.redirectUri,
157
+ scope: params.scope,
158
+ state: params.state,
159
+ code_challenge: params.codeChallenge,
160
+ code_challenge_method: params.codeChallengeMethod,
161
+ },
162
+ overridesJsonApiCreation: true,
163
+ });
164
+
165
+ return {
166
+ redirectUrl: result.meta?.redirectUrl as string,
167
+ };
168
+ }
169
+
170
+ /**
171
+ * Deny the authorization request
172
+ * @returns Redirect URL with error=access_denied
173
+ */
174
+ static async denyAuthorization(params: OAuthConsentRequest): Promise<{ redirectUrl: string }> {
175
+ const result = await this.callApiWithMeta<unknown>({
176
+ type: Modules.OAuth,
177
+ method: HttpMethod.POST,
178
+ endpoint: new EndpointCreator({ endpoint: "oauth/authorize/deny" }).generate(),
179
+ input: {
180
+ client_id: params.clientId,
181
+ redirect_uri: params.redirectUri,
182
+ state: params.state,
183
+ },
184
+ overridesJsonApiCreation: true,
185
+ });
186
+
187
+ return {
188
+ redirectUrl: result.meta?.redirectUrl as string,
189
+ };
190
+ }
191
+ }