first commit
This commit is contained in:
@@ -0,0 +1,383 @@
|
||||
# AuthHex API Documentation
|
||||
|
||||
Base route: `api`
|
||||
All endpoints are `[AllowAnonymous]` at the HTTP layer, but individual functions may require a Bearer JWT (read via `HttpContext` claims) — noted per function below.
|
||||
|
||||
## Common envelope
|
||||
|
||||
**Request** (`ApiRequest`)
|
||||
```json
|
||||
{
|
||||
"functionName": "string (required, ignored for dedicated routes below)",
|
||||
"payload": { "...": "function-specific object" },
|
||||
"reference": "string (required, can be empty)"
|
||||
}
|
||||
```
|
||||
|
||||
**Response** (`ApiResponse`)
|
||||
```json
|
||||
{
|
||||
"statusCode": 200,
|
||||
"success": true,
|
||||
"message": "success",
|
||||
"data": { "...": "function-specific object" }
|
||||
}
|
||||
```
|
||||
Errors: `404` unknown function, `400` (`KeyNotFoundException`/`InvalidOperationException`), `500` (`ArgumentException`).
|
||||
|
||||
---
|
||||
|
||||
## Routes
|
||||
|
||||
| Route | Method | Dispatches to | FunctionName |
|
||||
|---|---|---|---|
|
||||
| `/api/status` | GET | — | — (health check) |
|
||||
| `/api/user` | POST | UserManager | from body |
|
||||
| `/api/recovery` | POST | RecoveryManager | from body |
|
||||
| `/api/registerUser` | POST | UserManager | forced `registerUser` |
|
||||
| `/api/loginUser` | POST | UserManager | forced `loginUser` |
|
||||
| `/api/forgotPassword` | POST | RecoveryManager | forced `forgotPassword` |
|
||||
| `/api/alt` | POST | AltOptionManager | from body |
|
||||
| `/api/role` | POST | RoleManager | from body |
|
||||
|
||||
### GET /api/status
|
||||
Response:
|
||||
```json
|
||||
{ "status": "API is running", "timestamp": "2026-07-03T00:00:00Z" }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## /api/user — UserManager functions
|
||||
|
||||
### registerUser
|
||||
Payload:
|
||||
```json
|
||||
{
|
||||
"userId": "guid (required)",
|
||||
"fullname": "string?",
|
||||
"userName": "string?",
|
||||
"nic": "string?",
|
||||
"email": "string?",
|
||||
"mobileNumber": "string?",
|
||||
"deviceName": "string? (default 'Unknown Device')",
|
||||
"roleId": "guid (required)",
|
||||
"userTypeId": "guid (required)",
|
||||
"chkUser": "bool? (if true, checks for existing conflicting user)",
|
||||
"password": "string? (auto-generated if empty)"
|
||||
}
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"accessToken": "jwt",
|
||||
"refreshToken": "string",
|
||||
"expiresIn": 3600,
|
||||
"user": { "id": "guid", "fullName": "", "userName": "", "email": "", "mobileNumber": "", "emailVerified": false, "mobileNumberVerified": false, "isMfaEnabled": false, "roleId": "guid", "userTypeId": "guid" }
|
||||
}
|
||||
```
|
||||
|
||||
### loginUser
|
||||
Payload:
|
||||
```json
|
||||
{
|
||||
"identifier": "string (required)",
|
||||
"password": "string?",
|
||||
"userTypeId": "guid?",
|
||||
"deviceName": "string? (default 'Unknown Device')"
|
||||
}
|
||||
```
|
||||
Data: same shape as `registerUser` data.
|
||||
|
||||
### VerifyOtpForLogin
|
||||
Payload:
|
||||
```json
|
||||
{ "referenceNumber": "string (required)", "otpCode": "string (required)", "deviceName": "string?" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OTP verified successfully.",
|
||||
"data": {
|
||||
"referenceNumber": "", "userId": "guid", "verified": true,
|
||||
"accessToken": "jwt", "refreshToken": "", "expiresIn": 3600,
|
||||
"user": { "...": "as above" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### refreshToken
|
||||
Payload:
|
||||
```json
|
||||
{ "refreshToken": "string (required)", "deviceName": "string?" }
|
||||
```
|
||||
Data: same shape as `registerUser` data (rotates session).
|
||||
|
||||
### getUserDetails
|
||||
Payload:
|
||||
```json
|
||||
{ "userId": "guid (required)" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"id": "guid", "fullName": "", "userName": "", "email": "", "nic": "",
|
||||
"mobileNumber": "", "emailVerified": false, "mobileNumberVerified": false,
|
||||
"isMfaEnabled": false, "isActive": true, "isLocked": false, "createdAt": "",
|
||||
"role": { "roleId": "", "code": "", "name": "" },
|
||||
"userType": { "userTypeId": "", "code": "", "description": "" }
|
||||
}
|
||||
```
|
||||
|
||||
### getUserSessions
|
||||
_Requires auth (userId from claims). No payload needed._
|
||||
Data: array of
|
||||
```json
|
||||
{ "sessionId": "", "deviceName": "", "browser": "", "os": "", "ipAddress": "", "createdAt": "", "expiresAt": "", "revokedAt": "", "isActive": true }
|
||||
```
|
||||
|
||||
### ChangeUserStatus
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "isActive": "bool (required)" }
|
||||
```
|
||||
Data: `{ "id", "fullName", "userName", "email", "mobileNumber", "isActive" }`
|
||||
|
||||
### LockUserAccount
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "isLocked": "bool (required)" }
|
||||
```
|
||||
Data: `{ "id", "fullName", "userName", "email", "mobileNumber", "isLocked", "deletedAt" }`
|
||||
(Also invalidates all user sessions.)
|
||||
|
||||
### ChangeUserPassword
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "currentPassword": "string (required)", "newPassword": "string (required)" }
|
||||
```
|
||||
Data: `{ "message": "Password changed successfully" }`
|
||||
|
||||
### LogoutUser
|
||||
Payload:
|
||||
```json
|
||||
{ "userId": "guid (required)" }
|
||||
```
|
||||
Data: `{ "message": "User logged out successfully" }`
|
||||
|
||||
### UpdateUser
|
||||
_Requires auth._ Payload (all optional, at least one required):
|
||||
```json
|
||||
{
|
||||
"fullName": "string?", "userName": "string?", "nic": "string?", "address": "string?",
|
||||
"optional1": "string?", "optional2": "string?",
|
||||
"email": "string|null", "mobileNumber": "string|null",
|
||||
"currentPassword": "string? (required if newPassword set and existing password set)",
|
||||
"newPassword": "string?"
|
||||
}
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"message": "User updated successfully",
|
||||
"user": { "id", "fullName", "userName", "nic", "address", "optional1", "optional2", "email", "emailVerified", "mobileNumber", "mobileNumberVerified" }
|
||||
}
|
||||
```
|
||||
|
||||
### 2FA — initiateTwoFASetup
|
||||
_Requires auth. No payload._
|
||||
Data: third-party-service-defined setup result (QR/secret info).
|
||||
|
||||
### 2FA — completeTwoFASetup
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "secretKey": "string (required)", "verificationCode": "string (required)" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"message": "2FA setup completed successfully",
|
||||
"backupCodes": ["..."],
|
||||
"user": { "id", "fullName", "userName", "email", "isMfaEnabled" }
|
||||
}
|
||||
```
|
||||
|
||||
### 2FA — verifyTwoFA
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "verificationCode": "string (required)" }
|
||||
```
|
||||
Data: raw third-party verification result object.
|
||||
|
||||
### 2FA — disableTwoFA
|
||||
_Requires auth._ Payload:
|
||||
```json
|
||||
{ "verificationCode": "string (required)" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{ "message": "2FA has been disabled successfully", "user": { "id", "fullName", "userName", "email", "isMfaEnabled" } }
|
||||
```
|
||||
|
||||
### 2FA — getTwoFAStatus
|
||||
_Requires auth. No payload._
|
||||
Data:
|
||||
```json
|
||||
{ "isMfaEnabled": false, "isVerified": false, "lastUsedAt": null, "verifiedAt": null, "hasBackupCodes": false }
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Admin user management (userId-targeted)
|
||||
These act on an explicit `userId` in the payload (unlike `ChangeUserStatus`/`LockUserAccount`/`UpdateUser`,
|
||||
which act on the token's own user). Used by ERP-core's `UsersController`.
|
||||
|
||||
#### listUsers
|
||||
Payload: `{ "page": 1, "pageSize": 20, "q": "search?" }`. Data: `{ "items": [ { id, fullName, userName, email, mobileNumber, nic, isActive, isLocked, createdAt, roleId, roleCode, roleName, userTypeId, userTypeCode } ], "total": N, "page": 1, "pageSize": 20 }`.
|
||||
|
||||
#### adminUpdateUser
|
||||
Payload: `{ "userId": "guid (required)", "fullName?", "userName?", "nic?", "email?", "mobileNumber?", "address?", "roleId?", "userTypeId?", "newPassword?" }` — only keys present are applied. Data: the updated user summary.
|
||||
|
||||
#### adminSetStatus
|
||||
Payload: `{ "userId": "guid (required)", "isActive": true|false }`. Deactivating also invalidates the user's sessions. Data: `{ id, isActive }`.
|
||||
|
||||
#### adminSetLock
|
||||
Payload: `{ "userId": "guid (required)", "isLocked": true|false }`. Locking also invalidates the user's sessions. Data: `{ id, isLocked }`.
|
||||
|
||||
#### listUserTypes
|
||||
Payload: `{}`. Data: `[ { userTypeId, code, description } ]`. (Seeded at startup: `ERP_ADMIN`, `ERP_STAFF`.)
|
||||
|
||||
## /api/recovery — RecoveryManager functions
|
||||
|
||||
### forgotPassword
|
||||
Payload:
|
||||
```json
|
||||
{ "identifier": "string (required)", "useResetLink": "bool? (default false)", "numberOfDigits": "int? (default 6)" }
|
||||
```
|
||||
Data (useResetLink = true):
|
||||
```json
|
||||
{ "success": true, "message": "Password reset link sent to your email.", "data": { "referenceNumber": "", "expiresAt": "", "recoveryType": "ResetLink" } }
|
||||
```
|
||||
Data (useResetLink = false, OTP flow):
|
||||
```json
|
||||
{ "success": true, "message": "OTP sent successfully.", "data": { "referenceNumber": "", "expiresAt": "", "recoveryType": "OTP" } }
|
||||
```
|
||||
|
||||
### verifyOTP
|
||||
Payload:
|
||||
```json
|
||||
{ "referenceNumber": "string (required)", "otpCode": "string (required)" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{ "success": true, "message": "OTP verified successfully. You can now reset your password.", "data": { "referenceNumber": "", "userId": "guid", "verified": true } }
|
||||
```
|
||||
|
||||
### resetPasswordWithToken
|
||||
Payload:
|
||||
```json
|
||||
{ "resetToken": "string (required)", "newPassword": "string (required, min 8 chars)", "confirmPassword": "string (required, must match)" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{ "success": true, "message": "Password reset successful. Please login with your new password.", "data": { "userId": "guid", "email": "", "resetAt": "" } }
|
||||
```
|
||||
|
||||
### resetPassword
|
||||
Payload:
|
||||
```json
|
||||
{ "referenceNumber": "string (required)", "newPassword": "string (required, min 8 chars)", "confirmPassword": "string (required, must match)" }
|
||||
```
|
||||
(Requires recovery status = "Verified" via prior `verifyOTP` call.)
|
||||
Data: same shape as `resetPasswordWithToken`.
|
||||
|
||||
---
|
||||
|
||||
## /api/alt — AltOptionManager functions
|
||||
|
||||
### IsAvailable
|
||||
Payload:
|
||||
```json
|
||||
{ "Identifier": "string?", "Recovery": "string? (any non-null value flags recovery-mode)" }
|
||||
```
|
||||
Data (available): `{ "isAvailable": true, "message": "Identifier is available" }`
|
||||
Data (taken, no Recovery): `{ "isAvailable": false, "message": "Identifier already in use" }`
|
||||
Data (taken, Recovery set): `{ "existingUsers": [...] }`
|
||||
|
||||
### sendOtp
|
||||
Payload:
|
||||
```json
|
||||
{ "identifier": "string (required, email or mobile)", "numberOfDigits": "int? (default 4)", "newUser": "bool? (default false)" }
|
||||
```
|
||||
Data (newUser = true):
|
||||
```json
|
||||
{ "success": true, "message": "OTP sent successfully for new user.", "data": { "referenceNumber": "", "expiresAt": "", "recoveryType": "OTP FOR NEW USER" } }
|
||||
```
|
||||
Data (existing user):
|
||||
```json
|
||||
{ "success": true, "message": "OTP sent successfully.", "data": { "referenceNumber": "", "expiresAt": "", "recoveryType": "OTP FOR LOGIN" } }
|
||||
```
|
||||
|
||||
### VerifyOTP (alt)
|
||||
Payload:
|
||||
```json
|
||||
{
|
||||
"userId": "guid?", "referenceNumber": "string (required)", "otpCode": "string (required)",
|
||||
"newUser": "bool? (default false)", "identifier": "string? (email/mobile/other — marks it verified on user)",
|
||||
"deviceName": "string? (default 'Unknown Device')"
|
||||
}
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"message": "OTP verified successfully.",
|
||||
"data": {
|
||||
"referenceNumber": "", "userId": "guid", "verified": true,
|
||||
"accessToken": "jwt", "refreshToken": "", "expiresIn": 3600,
|
||||
"user": { "id", "fullName", "userName", "email", "mobileNumber", "emailVerified", "mobileNumberVerified", "isMfaEnabled", "roleId", "userTypeId" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## /api/role — RoleManager functions
|
||||
|
||||
AuthHex owns only the `Role` lookup table (identity source for the JWT `RoleId`/`RoleCode` claims).
|
||||
Roles are authored in the main system (**ERP-core**) and cloned here via `createRole` using the **same
|
||||
`RoleId`** so the two role stores stay in sync. This route is currently `AllowAnonymous` (server-to-server
|
||||
call from ERP-core); tighten it with a service credential when dual-auth is introduced.
|
||||
|
||||
### createRole
|
||||
Clone a role created in ERP-core. Accepts the caller-supplied `roleId` so the AuthHex row matches.
|
||||
Payload:
|
||||
```json
|
||||
{ "roleId": "guid (required)", "code": "string (required, unique)", "name": "string?", "isSystemRole": "bool?" }
|
||||
```
|
||||
Data:
|
||||
```json
|
||||
{ "roleId": "guid", "code": "ADMIN", "name": "Administrator", "isSystemRole": false, "createdAt": "2026-07-16T00:00:00Z" }
|
||||
```
|
||||
Errors: duplicate `roleId` or `code` → `409`; missing/invalid `roleId`/`code` → `400`.
|
||||
|
||||
### listRoles
|
||||
Payload: `{}` (none). Data: array of the role object shown above.
|
||||
|
||||
### getRole
|
||||
Payload: `{ "roleId": "guid (required)" }`. Data: the role object above; unknown id → `404`.
|
||||
|
||||
### deleteRole
|
||||
Payload: `{ "roleId": "guid (required)" }`. Deletes the role. Blocked with `409` if any user is still assigned
|
||||
(the `User.RoleId` FK is Restrict) so ERP-core — which calls this before removing its own copy — never orphans an
|
||||
in-use role. Data: `{ "roleId": "guid", "deleted": true }`; unknown id → `404`.
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
- All timestamps are UTC.
|
||||
- `deviceName`, `Browser`, `OS`, `IPAddress` are captured per session from request headers for session tracking (`getUserSessions`).
|
||||
- Password login validation is **active** — `loginUser` verifies the BCrypt hash via `PasswordHasher.Verify` and throws `Invalid credentials` on mismatch. Login identifiers are **email / mobile / NIC** (not username). Register with an explicit `password` (a blank one is randomized → unusable).
|
||||
- **Bootstrap seed** (idempotent, at startup — `Infra/Seed/AuthHexSeeder.cs`): user types `ERP_ADMIN`/`ERP_STAFF`, an `ADMIN` role (fixed GUID `11111111-…`, mirrored by ERP-core), and an admin user (`Bootstrap:AdminEmail` default `admin@erp.local` / `Bootstrap:AdminPassword` default `Admin@123`).
|
||||
- JWT access tokens issued with `expiresIn: 3600` (1 hour); refresh tokens/sessions expire after 30 days.
|
||||
Reference in New Issue
Block a user