- The first account on a new install becomes its owner. After that, each new email must be approved
- by whoever runs this install.
+ {registrationMode === 'open'
+ ? 'This development install allows any email to register. Registration does not verify email ownership.'
+ : registrationMode === 'approval'
+ ? 'After the initial setup account, new emails must be approved by whoever runs this install. Setup does not grant administrator privileges.'
+ : 'Registration depends on this installation: development permits any email; other environments require approval after initial setup.'}
diff --git a/apps/frontend/src/shared/services/api/auth.ts b/apps/frontend/src/shared/services/api/auth.ts
index ad7678d6..8bee6597 100644
--- a/apps/frontend/src/shared/services/api/auth.ts
+++ b/apps/frontend/src/shared/services/api/auth.ts
@@ -1,3 +1,4 @@
+import type { components } from './generated';
import { api } from './client';
import type { RequestConfig } from './client';
import type {
@@ -14,6 +15,10 @@ import type {
} from './types';
export const authService = {
+ registrationPolicy(config?: RequestConfig): Promise
{
+ return api.get('/auth/registration-policy', config);
+ },
+
register(request: RegisterRequest, config?: RequestConfig): Promise {
return api.post('/auth/register', request, config);
},
diff --git a/apps/frontend/src/shared/services/api/generated.ts b/apps/frontend/src/shared/services/api/generated.ts
index 3a53adf1..2ae40db9 100644
--- a/apps/frontend/src/shared/services/api/generated.ts
+++ b/apps/frontend/src/shared/services/api/generated.ts
@@ -518,6 +518,23 @@ export interface paths {
patch?: never;
trace?: never;
};
+ "/auth/registration-policy": {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ /** Registration Policy */
+ get: operations["registration_policy_auth_registration_policy_get"];
+ put?: never;
+ post?: never;
+ delete?: never;
+ options?: never;
+ head?: never;
+ patch?: never;
+ trace?: never;
+ };
"/documentation/generate": {
parameters: {
query?: never;
@@ -1919,6 +1936,14 @@ export interface components {
/** Password */
password: string;
};
+ /** RegistrationPolicyResponse */
+ RegistrationPolicyResponse: {
+ /**
+ * Mode
+ * @enum {string}
+ */
+ mode: "open" | "approval";
+ };
/** RepositoryFileResponse */
RepositoryFileResponse: {
/** Content */
@@ -6356,6 +6381,66 @@ export interface operations {
};
};
};
+ registration_policy_auth_registration_policy_get: {
+ parameters: {
+ query?: never;
+ header?: never;
+ path?: never;
+ cookie?: never;
+ };
+ requestBody?: never;
+ responses: {
+ /** @description Installation registration mode; no account or bootstrap state. */
+ 200: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ /**
+ * @example {
+ * "mode": "approval"
+ * }
+ */
+ "application/json": components["schemas"]["RegistrationPolicyResponse"];
+ };
+ };
+ /** @description The request-rate limit has been exceeded. */
+ 429: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ /**
+ * @example {
+ * "code": "rate_limited",
+ * "message": "Too many requests. Try again shortly.",
+ * "details": {
+ * "retryAfterSeconds": 30
+ * },
+ * "request_id": "req_01HXYZEXAMPLE"
+ * }
+ */
+ "application/json": components["schemas"]["ErrorResponse"];
+ };
+ };
+ /** @description An unexpected server error occurred. */
+ 500: {
+ headers: {
+ [name: string]: unknown;
+ };
+ content: {
+ /**
+ * @example {
+ * "code": "internal_server_error",
+ * "message": "An unexpected error occurred.",
+ * "request_id": "req_01HXYZEXAMPLE"
+ * }
+ */
+ "application/json": components["schemas"]["ErrorResponse"];
+ };
+ };
+ };
+ };
generate_documentation_documentation_generate_post: {
parameters: {
query?: never;