@checkstack/auth-common 0.0.2
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.
- package/CHANGELOG.md +114 -0
- package/package.json +26 -0
- package/src/index.ts +5 -0
- package/src/permissions.ts +50 -0
- package/src/plugin-metadata.ts +9 -0
- package/src/routes.ts +14 -0
- package/src/rpc-contract.ts +395 -0
- package/src/schemas.ts +20 -0
- package/tsconfig.json +6 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# @checkstack/auth-common
|
|
2
|
+
|
|
3
|
+
## 0.0.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- d20d274: Initial release of all @checkstack packages. Rebranded from Checkmate to Checkstack with new npm organization @checkstack and domain checkstack.dev.
|
|
8
|
+
- Updated dependencies [d20d274]
|
|
9
|
+
- @checkstack/common@0.0.2
|
|
10
|
+
|
|
11
|
+
## 0.2.1
|
|
12
|
+
|
|
13
|
+
### Patch Changes
|
|
14
|
+
|
|
15
|
+
- a65e002: Add compile-time type safety for Lucide icon names
|
|
16
|
+
|
|
17
|
+
- Add `LucideIconName` type and `lucideIconSchema` Zod schema to `@checkstack/common`
|
|
18
|
+
- Update backend interfaces (`AuthStrategy`, `NotificationStrategy`, `IntegrationProvider`, `CommandDefinition`) to use `LucideIconName`
|
|
19
|
+
- Update RPC contracts to use `lucideIconSchema` for proper type inference across RPC boundaries
|
|
20
|
+
- Simplify `SocialProviderButton` to use `DynamicIcon` directly (removes 30+ lines of pascalCase conversion)
|
|
21
|
+
- Replace static `iconMap` in `SearchDialog` with `DynamicIcon` for dynamic icon rendering
|
|
22
|
+
- Add fallback handling in `DynamicIcon` when icon name isn't found
|
|
23
|
+
- Fix legacy kebab-case icon names to PascalCase: `mail`→`Mail`, `send`→`Send`, `github`→`Github`, `key-round`→`KeyRound`, `network`→`Network`, `AlertCircle`→`CircleAlert`
|
|
24
|
+
|
|
25
|
+
- Updated dependencies [a65e002]
|
|
26
|
+
- @checkstack/common@0.2.0
|
|
27
|
+
|
|
28
|
+
## 0.2.0
|
|
29
|
+
|
|
30
|
+
### Minor Changes
|
|
31
|
+
|
|
32
|
+
- e26c08e: Add password change functionality for credential-authenticated users
|
|
33
|
+
|
|
34
|
+
- Add `changePassword` route to auth-common
|
|
35
|
+
- Create `ChangePasswordPage.tsx` component with password validation, current password verification, and session revocation option
|
|
36
|
+
- Add "Change Password" menu item in User Menu
|
|
37
|
+
- Reuses patterns from existing password reset flow for consistency
|
|
38
|
+
|
|
39
|
+
## 0.1.0
|
|
40
|
+
|
|
41
|
+
### Minor Changes
|
|
42
|
+
|
|
43
|
+
- 32f2535: Refactor application role assignment
|
|
44
|
+
|
|
45
|
+
- Removed role selection from the application creation dialog
|
|
46
|
+
- New applications now automatically receive the "Applications" role
|
|
47
|
+
- Roles are now manageable inline in the Applications table (similar to user role management)
|
|
48
|
+
- Added informational alert in create dialog explaining default role behavior
|
|
49
|
+
|
|
50
|
+
- b354ab3: # Strategy Instructions Support & Telegram Notification Plugin
|
|
51
|
+
|
|
52
|
+
## Strategy Instructions Interface
|
|
53
|
+
|
|
54
|
+
Added `adminInstructions` and `userInstructions` optional fields to the `NotificationStrategy` interface. These allow strategies to export markdown-formatted setup guides that are displayed in the configuration UI:
|
|
55
|
+
|
|
56
|
+
- **`adminInstructions`**: Shown when admins configure platform-wide strategy settings (e.g., how to create API keys)
|
|
57
|
+
- **`userInstructions`**: Shown when users configure their personal settings (e.g., how to link their account)
|
|
58
|
+
|
|
59
|
+
### Updated Components
|
|
60
|
+
|
|
61
|
+
- `StrategyConfigCard` now accepts an `instructions` prop and renders it before config sections
|
|
62
|
+
- `StrategyCard` passes `adminInstructions` to `StrategyConfigCard`
|
|
63
|
+
- `UserChannelCard` renders `userInstructions` when users need to connect
|
|
64
|
+
|
|
65
|
+
## New Telegram Notification Plugin
|
|
66
|
+
|
|
67
|
+
Added `@checkstack/notification-telegram-backend` plugin for sending notifications via Telegram:
|
|
68
|
+
|
|
69
|
+
- Uses [grammY](https://grammy.dev/) framework for Telegram Bot API integration
|
|
70
|
+
- Sends messages with MarkdownV2 formatting and inline keyboard buttons for actions
|
|
71
|
+
- Includes comprehensive admin instructions for bot setup via @BotFather
|
|
72
|
+
- Includes user instructions for account linking
|
|
73
|
+
|
|
74
|
+
### Configuration
|
|
75
|
+
|
|
76
|
+
Admins need to configure a Telegram Bot Token obtained from @BotFather.
|
|
77
|
+
|
|
78
|
+
### User Linking
|
|
79
|
+
|
|
80
|
+
The strategy uses `contactResolution: { type: "custom" }` for Telegram Login Widget integration. Full frontend integration for the Login Widget is pending future work.
|
|
81
|
+
|
|
82
|
+
### Patch Changes
|
|
83
|
+
|
|
84
|
+
- ffc28f6: ### Anonymous Role and Public Access
|
|
85
|
+
|
|
86
|
+
Introduces a configurable "anonymous" role for managing permissions available to unauthenticated users.
|
|
87
|
+
|
|
88
|
+
**Core Changes:**
|
|
89
|
+
|
|
90
|
+
- Added `userType: "public"` - endpoints accessible by both authenticated users (with their permissions) and anonymous users (with anonymous role permissions)
|
|
91
|
+
- Renamed `userType: "both"` to `"authenticated"` for clarity
|
|
92
|
+
- Renamed `isDefault` to `isAuthenticatedDefault` on Permission interface
|
|
93
|
+
- Added `isPublicDefault` flag for permissions that should be granted to the anonymous role by default
|
|
94
|
+
|
|
95
|
+
**Backend Infrastructure:**
|
|
96
|
+
|
|
97
|
+
- New `anonymous` system role created during auth-backend initialization
|
|
98
|
+
- New `disabled_public_default_permission` table tracks admin-disabled public defaults
|
|
99
|
+
- `autoAuthMiddleware` now checks anonymous role permissions for unauthenticated public endpoint access
|
|
100
|
+
- `AuthService.getAnonymousPermissions()` with 1-minute caching for performance
|
|
101
|
+
- Anonymous role filtered from `getRoles` endpoint (not assignable to users)
|
|
102
|
+
- Validation prevents assigning anonymous role to users
|
|
103
|
+
|
|
104
|
+
**Catalog Integration:**
|
|
105
|
+
|
|
106
|
+
- `catalog.read` permission now has both `isAuthenticatedDefault` and `isPublicDefault`
|
|
107
|
+
- Read endpoints (`getSystems`, `getGroups`, `getEntities`) now use `userType: "public"`
|
|
108
|
+
|
|
109
|
+
**UI:**
|
|
110
|
+
|
|
111
|
+
- New `PermissionGate` component for conditionally rendering content based on permissions
|
|
112
|
+
|
|
113
|
+
- Updated dependencies [ffc28f6]
|
|
114
|
+
- @checkstack/common@0.1.0
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@checkstack/auth-common",
|
|
3
|
+
"version": "0.0.2",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
|
+
"import": "./src/index.ts"
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
"dependencies": {
|
|
12
|
+
"@checkstack/common": "workspace:*",
|
|
13
|
+
"@orpc/contract": "^1.13.2",
|
|
14
|
+
"zod": "^4.0.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@checkstack/tsconfig": "workspace:*",
|
|
18
|
+
"typescript": "^5.7.2",
|
|
19
|
+
"@checkstack/scripts": "workspace:*"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "bun run lint:code",
|
|
24
|
+
"lint:code": "eslint . --max-warnings 0"
|
|
25
|
+
}
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Permission } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
export const permissions = {
|
|
4
|
+
usersRead: {
|
|
5
|
+
id: "users.read",
|
|
6
|
+
description: "List all users",
|
|
7
|
+
},
|
|
8
|
+
usersCreate: {
|
|
9
|
+
id: "users.create",
|
|
10
|
+
description: "Create new users (credential strategy)",
|
|
11
|
+
},
|
|
12
|
+
usersManage: {
|
|
13
|
+
id: "users.manage",
|
|
14
|
+
description: "Delete users",
|
|
15
|
+
},
|
|
16
|
+
rolesRead: {
|
|
17
|
+
id: "roles.read",
|
|
18
|
+
description: "Read and list roles",
|
|
19
|
+
},
|
|
20
|
+
rolesCreate: {
|
|
21
|
+
id: "roles.create",
|
|
22
|
+
description: "Create new roles",
|
|
23
|
+
},
|
|
24
|
+
rolesUpdate: {
|
|
25
|
+
id: "roles.update",
|
|
26
|
+
description: "Update role names and permissions",
|
|
27
|
+
},
|
|
28
|
+
rolesDelete: {
|
|
29
|
+
id: "roles.delete",
|
|
30
|
+
description: "Delete roles",
|
|
31
|
+
},
|
|
32
|
+
rolesManage: {
|
|
33
|
+
id: "roles.manage",
|
|
34
|
+
description: "Assign roles to users",
|
|
35
|
+
},
|
|
36
|
+
strategiesManage: {
|
|
37
|
+
id: "strategies.manage",
|
|
38
|
+
description: "Manage authentication strategies and settings",
|
|
39
|
+
},
|
|
40
|
+
registrationManage: {
|
|
41
|
+
id: "registration.manage",
|
|
42
|
+
description: "Manage user registration settings",
|
|
43
|
+
},
|
|
44
|
+
applicationsManage: {
|
|
45
|
+
id: "applications.manage",
|
|
46
|
+
description: "Create, update, delete, and view external applications",
|
|
47
|
+
},
|
|
48
|
+
} satisfies Record<string, Permission>;
|
|
49
|
+
|
|
50
|
+
export const permissionList = Object.values(permissions);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { definePluginMetadata } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Plugin metadata for the auth plugin.
|
|
5
|
+
* Exported from the common package so both backend and frontend can reference it.
|
|
6
|
+
*/
|
|
7
|
+
export const pluginMetadata = definePluginMetadata({
|
|
8
|
+
pluginId: "auth",
|
|
9
|
+
});
|
package/src/routes.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { createRoutes } from "@checkstack/common";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Route definitions for the auth plugin.
|
|
5
|
+
*/
|
|
6
|
+
export const authRoutes = createRoutes("auth", {
|
|
7
|
+
login: "/login",
|
|
8
|
+
register: "/register",
|
|
9
|
+
error: "/error",
|
|
10
|
+
settings: "/settings",
|
|
11
|
+
forgotPassword: "/forgot-password",
|
|
12
|
+
resetPassword: "/reset-password",
|
|
13
|
+
changePassword: "/change-password",
|
|
14
|
+
});
|
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import { oc } from "@orpc/contract";
|
|
2
|
+
import {
|
|
3
|
+
createClientDefinition,
|
|
4
|
+
type ProcedureMetadata,
|
|
5
|
+
lucideIconSchema,
|
|
6
|
+
} from "@checkstack/common";
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { permissions } from "./permissions";
|
|
9
|
+
import { pluginMetadata } from "./plugin-metadata";
|
|
10
|
+
|
|
11
|
+
// Base builder with full metadata support
|
|
12
|
+
const _base = oc.$meta<ProcedureMetadata>({});
|
|
13
|
+
|
|
14
|
+
// Zod schemas for return types
|
|
15
|
+
const UserDtoSchema = z.object({
|
|
16
|
+
id: z.string(),
|
|
17
|
+
email: z.string(),
|
|
18
|
+
name: z.string(),
|
|
19
|
+
roles: z.array(z.string()),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
const RoleDtoSchema = z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
name: z.string(),
|
|
25
|
+
description: z.string().optional().nullable(),
|
|
26
|
+
permissions: z.array(z.string()),
|
|
27
|
+
isSystem: z.boolean().optional(),
|
|
28
|
+
isAssignable: z.boolean().optional(), // False for anonymous role
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const PermissionDtoSchema = z.object({
|
|
32
|
+
id: z.string(),
|
|
33
|
+
description: z.string().optional(),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
const StrategyDtoSchema = z.object({
|
|
37
|
+
id: z.string(),
|
|
38
|
+
displayName: z.string(),
|
|
39
|
+
description: z.string().optional(),
|
|
40
|
+
icon: lucideIconSchema.optional(),
|
|
41
|
+
enabled: z.boolean(),
|
|
42
|
+
configVersion: z.number(),
|
|
43
|
+
configSchema: z.record(z.string(), z.unknown()), // JSON Schema representation
|
|
44
|
+
config: z.record(z.string(), z.unknown()).optional(), // VersionedConfig.data (secrets redacted)
|
|
45
|
+
adminInstructions: z.string().optional(), // Markdown instructions for admins
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const EnabledStrategyDtoSchema = z.object({
|
|
49
|
+
id: z.string(),
|
|
50
|
+
displayName: z.string(),
|
|
51
|
+
description: z.string().optional(),
|
|
52
|
+
type: z.enum(["credential", "social"]),
|
|
53
|
+
icon: lucideIconSchema.optional(),
|
|
54
|
+
requiresManualRegistration: z.boolean(),
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const RegistrationStatusSchema = z.object({
|
|
58
|
+
allowRegistration: z.boolean(),
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ==========================================================================
|
|
62
|
+
// SERVICE-TO-SERVICE SCHEMAS (for auth provider plugins like LDAP)
|
|
63
|
+
// ==========================================================================
|
|
64
|
+
|
|
65
|
+
const FindUserByEmailInputSchema = z.object({
|
|
66
|
+
email: z.string().email(),
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const FindUserByEmailOutputSchema = z
|
|
70
|
+
.object({
|
|
71
|
+
id: z.string(),
|
|
72
|
+
})
|
|
73
|
+
.optional();
|
|
74
|
+
|
|
75
|
+
const UpsertExternalUserInputSchema = z.object({
|
|
76
|
+
email: z.string().email(),
|
|
77
|
+
name: z.string(),
|
|
78
|
+
providerId: z.string(), // e.g., "ldap"
|
|
79
|
+
accountId: z.string(), // Provider-specific account ID (e.g., LDAP username)
|
|
80
|
+
password: z.string(), // Hashed password
|
|
81
|
+
autoUpdateUser: z.boolean().optional(), // Update existing user's name
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const UpsertExternalUserOutputSchema = z.object({
|
|
85
|
+
userId: z.string(),
|
|
86
|
+
created: z.boolean(), // true if new user was created, false if existing
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const CreateSessionInputSchema = z.object({
|
|
90
|
+
userId: z.string(),
|
|
91
|
+
token: z.string(),
|
|
92
|
+
expiresAt: z.coerce.date(),
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
const CreateCredentialUserInputSchema = z.object({
|
|
96
|
+
email: z.string().email(),
|
|
97
|
+
name: z.string().min(1),
|
|
98
|
+
password: z.string(), // Validated against passwordSchema on backend
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
const CreateCredentialUserOutputSchema = z.object({
|
|
102
|
+
userId: z.string(),
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// Auth RPC Contract with full metadata
|
|
106
|
+
export const authContract = {
|
|
107
|
+
// ==========================================================================
|
|
108
|
+
// ANONYMOUS ENDPOINTS (userType: "anonymous")
|
|
109
|
+
// These can be called without authentication (login/registration pages)
|
|
110
|
+
// ==========================================================================
|
|
111
|
+
|
|
112
|
+
getEnabledStrategies: _base
|
|
113
|
+
.meta({ userType: "anonymous" })
|
|
114
|
+
.output(z.array(EnabledStrategyDtoSchema)),
|
|
115
|
+
|
|
116
|
+
getRegistrationStatus: _base
|
|
117
|
+
.meta({ userType: "anonymous" })
|
|
118
|
+
.output(RegistrationStatusSchema),
|
|
119
|
+
|
|
120
|
+
// ==========================================================================
|
|
121
|
+
// AUTHENTICATED ENDPOINTS (userType: "authenticated" - no specific permission)
|
|
122
|
+
// ==========================================================================
|
|
123
|
+
|
|
124
|
+
permissions: _base
|
|
125
|
+
.meta({ userType: "authenticated" }) // Any authenticated user can check their own permissions
|
|
126
|
+
.output(z.object({ permissions: z.array(z.string()) })),
|
|
127
|
+
|
|
128
|
+
// ==========================================================================
|
|
129
|
+
// USER MANAGEMENT (userType: "user" with permissions)
|
|
130
|
+
// ==========================================================================
|
|
131
|
+
|
|
132
|
+
getUsers: _base
|
|
133
|
+
.meta({ userType: "user", permissions: [permissions.usersRead.id] })
|
|
134
|
+
.output(z.array(UserDtoSchema)),
|
|
135
|
+
|
|
136
|
+
deleteUser: _base
|
|
137
|
+
.meta({ userType: "user", permissions: [permissions.usersManage.id] })
|
|
138
|
+
.input(z.string())
|
|
139
|
+
.output(z.void()),
|
|
140
|
+
|
|
141
|
+
createCredentialUser: _base
|
|
142
|
+
.meta({ userType: "user", permissions: [permissions.usersCreate.id] })
|
|
143
|
+
.input(CreateCredentialUserInputSchema)
|
|
144
|
+
.output(CreateCredentialUserOutputSchema),
|
|
145
|
+
|
|
146
|
+
updateUserRoles: _base
|
|
147
|
+
.meta({ userType: "user", permissions: [permissions.usersManage.id] })
|
|
148
|
+
.input(
|
|
149
|
+
z.object({
|
|
150
|
+
userId: z.string(),
|
|
151
|
+
roles: z.array(z.string()),
|
|
152
|
+
})
|
|
153
|
+
)
|
|
154
|
+
.output(z.void()),
|
|
155
|
+
|
|
156
|
+
// ==========================================================================
|
|
157
|
+
// ROLE MANAGEMENT (userType: "user" with permissions)
|
|
158
|
+
// ==========================================================================
|
|
159
|
+
|
|
160
|
+
getRoles: _base
|
|
161
|
+
.meta({ userType: "user", permissions: [permissions.rolesRead.id] })
|
|
162
|
+
.output(z.array(RoleDtoSchema)),
|
|
163
|
+
|
|
164
|
+
getPermissions: _base
|
|
165
|
+
.meta({ userType: "user", permissions: [permissions.rolesRead.id] })
|
|
166
|
+
.output(z.array(PermissionDtoSchema)),
|
|
167
|
+
|
|
168
|
+
createRole: _base
|
|
169
|
+
.meta({ userType: "user", permissions: [permissions.rolesCreate.id] })
|
|
170
|
+
.input(
|
|
171
|
+
z.object({
|
|
172
|
+
name: z.string(),
|
|
173
|
+
description: z.string().optional(),
|
|
174
|
+
permissions: z.array(z.string()),
|
|
175
|
+
})
|
|
176
|
+
)
|
|
177
|
+
.output(z.void()),
|
|
178
|
+
|
|
179
|
+
updateRole: _base
|
|
180
|
+
.meta({ userType: "user", permissions: [permissions.rolesUpdate.id] })
|
|
181
|
+
.input(
|
|
182
|
+
z.object({
|
|
183
|
+
id: z.string(),
|
|
184
|
+
name: z.string().optional(),
|
|
185
|
+
description: z.string().optional(),
|
|
186
|
+
permissions: z.array(z.string()),
|
|
187
|
+
})
|
|
188
|
+
)
|
|
189
|
+
.output(z.void()),
|
|
190
|
+
|
|
191
|
+
deleteRole: _base
|
|
192
|
+
.meta({ userType: "user", permissions: [permissions.rolesDelete.id] })
|
|
193
|
+
.input(z.string())
|
|
194
|
+
.output(z.void()),
|
|
195
|
+
|
|
196
|
+
// ==========================================================================
|
|
197
|
+
// STRATEGY MANAGEMENT (userType: "user" with permissions)
|
|
198
|
+
// ==========================================================================
|
|
199
|
+
|
|
200
|
+
getStrategies: _base
|
|
201
|
+
.meta({ userType: "user", permissions: [permissions.strategiesManage.id] })
|
|
202
|
+
.output(z.array(StrategyDtoSchema)),
|
|
203
|
+
|
|
204
|
+
updateStrategy: _base
|
|
205
|
+
.meta({ userType: "user", permissions: [permissions.strategiesManage.id] })
|
|
206
|
+
.input(
|
|
207
|
+
z.object({
|
|
208
|
+
id: z.string(),
|
|
209
|
+
enabled: z.boolean(),
|
|
210
|
+
config: z.record(z.string(), z.unknown()).optional(),
|
|
211
|
+
})
|
|
212
|
+
)
|
|
213
|
+
.output(z.object({ success: z.boolean() })),
|
|
214
|
+
|
|
215
|
+
reloadAuth: _base
|
|
216
|
+
.meta({ userType: "user", permissions: [permissions.strategiesManage.id] })
|
|
217
|
+
.output(z.object({ success: z.boolean() })),
|
|
218
|
+
|
|
219
|
+
// ==========================================================================
|
|
220
|
+
// REGISTRATION MANAGEMENT (userType: "user" with permissions)
|
|
221
|
+
// ==========================================================================
|
|
222
|
+
|
|
223
|
+
getRegistrationSchema: _base
|
|
224
|
+
.meta({
|
|
225
|
+
userType: "user",
|
|
226
|
+
permissions: [permissions.registrationManage.id],
|
|
227
|
+
})
|
|
228
|
+
.output(z.record(z.string(), z.unknown())),
|
|
229
|
+
|
|
230
|
+
setRegistrationStatus: _base
|
|
231
|
+
.meta({
|
|
232
|
+
userType: "user",
|
|
233
|
+
permissions: [permissions.registrationManage.id],
|
|
234
|
+
})
|
|
235
|
+
.input(RegistrationStatusSchema)
|
|
236
|
+
.output(z.object({ success: z.boolean() })),
|
|
237
|
+
|
|
238
|
+
// ==========================================================================
|
|
239
|
+
// INTERNAL SERVICE ENDPOINTS (userType: "service")
|
|
240
|
+
// ==========================================================================
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get permissions assigned to the anonymous role.
|
|
244
|
+
* Used by core AuthService for permission checks on public endpoints.
|
|
245
|
+
*/
|
|
246
|
+
getAnonymousPermissions: _base
|
|
247
|
+
.meta({ userType: "service" })
|
|
248
|
+
.output(z.array(z.string())),
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Find a user by email address.
|
|
252
|
+
* Used by external auth providers (e.g., LDAP) to check if a user exists.
|
|
253
|
+
*/
|
|
254
|
+
findUserByEmail: _base
|
|
255
|
+
.meta({ userType: "service" })
|
|
256
|
+
.input(FindUserByEmailInputSchema)
|
|
257
|
+
.output(FindUserByEmailOutputSchema),
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Upsert a user from an external auth provider.
|
|
261
|
+
* Creates user + account if new, or updates user if autoUpdateUser is true.
|
|
262
|
+
* Used by external auth providers (e.g., LDAP) to sync users.
|
|
263
|
+
*/
|
|
264
|
+
upsertExternalUser: _base
|
|
265
|
+
.meta({ userType: "service" })
|
|
266
|
+
.input(UpsertExternalUserInputSchema)
|
|
267
|
+
.output(UpsertExternalUserOutputSchema),
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Create a session for a user.
|
|
271
|
+
* Used by external auth providers (e.g., LDAP) after successful authentication.
|
|
272
|
+
*/
|
|
273
|
+
createSession: _base
|
|
274
|
+
.meta({ userType: "service" })
|
|
275
|
+
.input(CreateSessionInputSchema)
|
|
276
|
+
.output(z.object({ sessionId: z.string() })),
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get a user by their ID.
|
|
280
|
+
* Used by notification backend to fetch user email for contact resolution.
|
|
281
|
+
*/
|
|
282
|
+
getUserById: _base
|
|
283
|
+
.meta({ userType: "service" })
|
|
284
|
+
.input(z.object({ userId: z.string() }))
|
|
285
|
+
.output(
|
|
286
|
+
z
|
|
287
|
+
.object({
|
|
288
|
+
id: z.string(),
|
|
289
|
+
email: z.string(),
|
|
290
|
+
name: z.string().nullable(),
|
|
291
|
+
})
|
|
292
|
+
.optional()
|
|
293
|
+
),
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Filter a list of user IDs to only those who have a specific permission.
|
|
297
|
+
* Used by SignalService to send signals only to authorized users.
|
|
298
|
+
*/
|
|
299
|
+
filterUsersByPermission: _base
|
|
300
|
+
.meta({ userType: "service" })
|
|
301
|
+
.input(
|
|
302
|
+
z.object({
|
|
303
|
+
userIds: z.array(z.string()),
|
|
304
|
+
permission: z.string(), // Fully-qualified permission ID
|
|
305
|
+
})
|
|
306
|
+
)
|
|
307
|
+
.output(z.array(z.string())), // Returns filtered user IDs
|
|
308
|
+
|
|
309
|
+
// ==========================================================================
|
|
310
|
+
// APPLICATION MANAGEMENT (userType: "user" with permissions)
|
|
311
|
+
// External API applications (API keys) with RBAC integration
|
|
312
|
+
// ==========================================================================
|
|
313
|
+
|
|
314
|
+
getApplications: _base
|
|
315
|
+
.meta({
|
|
316
|
+
userType: "user",
|
|
317
|
+
permissions: [permissions.applicationsManage.id],
|
|
318
|
+
})
|
|
319
|
+
.output(
|
|
320
|
+
z.array(
|
|
321
|
+
z.object({
|
|
322
|
+
id: z.string(),
|
|
323
|
+
name: z.string(),
|
|
324
|
+
description: z.string().optional().nullable(),
|
|
325
|
+
roles: z.array(z.string()),
|
|
326
|
+
createdById: z.string(),
|
|
327
|
+
createdAt: z.coerce.date(),
|
|
328
|
+
lastUsedAt: z.coerce.date().optional().nullable(),
|
|
329
|
+
})
|
|
330
|
+
)
|
|
331
|
+
),
|
|
332
|
+
|
|
333
|
+
createApplication: _base
|
|
334
|
+
.meta({
|
|
335
|
+
userType: "user",
|
|
336
|
+
permissions: [permissions.applicationsManage.id],
|
|
337
|
+
})
|
|
338
|
+
.input(
|
|
339
|
+
z.object({
|
|
340
|
+
name: z.string().min(1).max(100),
|
|
341
|
+
description: z.string().max(500).optional(),
|
|
342
|
+
})
|
|
343
|
+
)
|
|
344
|
+
.output(
|
|
345
|
+
z.object({
|
|
346
|
+
application: z.object({
|
|
347
|
+
id: z.string(),
|
|
348
|
+
name: z.string(),
|
|
349
|
+
description: z.string().optional().nullable(),
|
|
350
|
+
roles: z.array(z.string()),
|
|
351
|
+
createdById: z.string(),
|
|
352
|
+
createdAt: z.coerce.date(),
|
|
353
|
+
}),
|
|
354
|
+
secret: z.string(), // Full secret - ONLY shown once!
|
|
355
|
+
})
|
|
356
|
+
),
|
|
357
|
+
|
|
358
|
+
updateApplication: _base
|
|
359
|
+
.meta({
|
|
360
|
+
userType: "user",
|
|
361
|
+
permissions: [permissions.applicationsManage.id],
|
|
362
|
+
})
|
|
363
|
+
.input(
|
|
364
|
+
z.object({
|
|
365
|
+
id: z.string(),
|
|
366
|
+
name: z.string().optional(),
|
|
367
|
+
description: z.string().optional().nullable(),
|
|
368
|
+
roles: z.array(z.string()).optional(),
|
|
369
|
+
})
|
|
370
|
+
)
|
|
371
|
+
.output(z.void()),
|
|
372
|
+
|
|
373
|
+
deleteApplication: _base
|
|
374
|
+
.meta({
|
|
375
|
+
userType: "user",
|
|
376
|
+
permissions: [permissions.applicationsManage.id],
|
|
377
|
+
})
|
|
378
|
+
.input(z.string())
|
|
379
|
+
.output(z.void()),
|
|
380
|
+
|
|
381
|
+
regenerateApplicationSecret: _base
|
|
382
|
+
.meta({
|
|
383
|
+
userType: "user",
|
|
384
|
+
permissions: [permissions.applicationsManage.id],
|
|
385
|
+
})
|
|
386
|
+
.input(z.string())
|
|
387
|
+
.output(z.object({ secret: z.string() })), // New secret - shown once
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// Export contract type
|
|
391
|
+
export type AuthContract = typeof authContract;
|
|
392
|
+
|
|
393
|
+
// Export client definition for type-safe forPlugin usage
|
|
394
|
+
// Use: const client = rpcApi.forPlugin(AuthApi);
|
|
395
|
+
export const AuthApi = createClientDefinition(authContract, pluginMetadata);
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Password validation schema with security requirements.
|
|
5
|
+
* Used for both registration and password reset flows.
|
|
6
|
+
*
|
|
7
|
+
* Requirements:
|
|
8
|
+
* - Minimum 8 characters
|
|
9
|
+
* - At least one uppercase letter
|
|
10
|
+
* - At least one lowercase letter
|
|
11
|
+
* - At least one number
|
|
12
|
+
*/
|
|
13
|
+
export const passwordSchema = z
|
|
14
|
+
.string()
|
|
15
|
+
.min(8, "Password must be at least 8 characters")
|
|
16
|
+
.regex(/[A-Z]/, "Password must contain at least one uppercase letter")
|
|
17
|
+
.regex(/[a-z]/, "Password must contain at least one lowercase letter")
|
|
18
|
+
.regex(/[0-9]/, "Password must contain at least one number");
|
|
19
|
+
|
|
20
|
+
export type Password = z.infer<typeof passwordSchema>;
|