diff --git a/bruno/user/Register Admin User.bru b/bruno/user/Register Admin User.bru deleted file mode 100644 index 04ad418..0000000 --- a/bruno/user/Register Admin User.bru +++ /dev/null @@ -1,68 +0,0 @@ -meta { - name: Register Admin User - type: http - seq: 4 -} - -post { - url: {{base_url}}/api/auth/register - body: json - auth: inherit -} - -body:json { - { - "email": "admin@example.com", - "username": "admin", - "password": "admin123", - "first_name": "Admin", - "last_name": "User", - "role": "admin" - } -} - -script:post-response { - function onResponse(res) { - let data = res.getBody(); - return bru.setEnvVar("token", data.token, { persist: true }); - } - onResponse(res); -} - -settings { - encodeUrl: true - timeout: 0 -} - -docs { - ## Register Admin User - - Creates a new admin user account. - - **Method:** POST - - **Endpoint:** /api/auth/register - - **Request Body:** - - `email` (string): Email address - - `username` (string): Username - - `password` (string): Password - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `role` (string): Must be "admin" - - **Response:** - - `token` (string): JWT token with admin role - - `user` (object): User details - - `id` (string): User ID - - `email` (string): Email - - `username` (string): Username - - `theme` (string): User theme preference - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `role` (string): User role ("admin") - - **Status Codes:** - - 201: Created - - 409: User exists -} \ No newline at end of file diff --git a/bruno/user/Register User.bru b/bruno/user/Register User.bru deleted file mode 100644 index d6a74cd..0000000 --- a/bruno/user/Register User.bru +++ /dev/null @@ -1,67 +0,0 @@ -meta { - name: Register User - type: http - seq: 3 -} - -post { - url: {{base_url}}/api/auth/register - body: json - auth: inherit -} - -body:json { - { - "email": "test@example.com", - "username": "testuser", - "password": "password123", - "first_name": "Test", - "last_name": "User" - } -} - -script:post-response { - function onResponse(res) { - let data = res.getBody(); - return bru.setEnvVar("token", data.token, { persist: true }); - } - onResponse(res); -} - -settings { - encodeUrl: true - timeout: 0 -} - -docs { - ## Register User - - Creates a new user account. - - **Method:** POST - - **Endpoint:** /api/auth/register - - **Request Body:** - - `email` (string): Email address - - `username` (string): Username - - `password` (string): Password - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `role` (string): User role ("user" or "admin") - - **Response:** - - `token` (string): JWT token - - `user` (object): User details - - `id` (string): User ID - - `email` (string): Email - - `username` (string): Username - - `theme` (string): User theme preference - - `first_name` (string, optional): First name - - `last_name` (string, optional): Last name - - `role` (string): User role ("user" or "admin") - - **Status Codes:** - - 201: Created - - 409: User exists -} diff --git a/bruno/user/Delete Account.bru b/bruno/user/admin/Delete Account.bru similarity index 100% rename from bruno/user/Delete Account.bru rename to bruno/user/admin/Delete Account.bru diff --git a/bruno/user/Delete User Account (Admin).bru b/bruno/user/admin/Delete User Account (Admin).bru similarity index 100% rename from bruno/user/Delete User Account (Admin).bru rename to bruno/user/admin/Delete User Account (Admin).bru diff --git a/bruno/user/List Users.bru b/bruno/user/admin/List Users.bru similarity index 100% rename from bruno/user/List Users.bru rename to bruno/user/admin/List Users.bru diff --git a/bruno/user/admin/Register Admin User.bru b/bruno/user/admin/Register Admin User.bru new file mode 100644 index 0000000..c566e4e --- /dev/null +++ b/bruno/user/admin/Register Admin User.bru @@ -0,0 +1,81 @@ +meta { + name: Register Admin User + type: http + seq: 4 +} + +post { + url: {{base_url}}/api/auth/register + body: json + auth: inherit +} + +body:json { + { + "email": "admin@example.com", + "username": "admin", + "password": "admin123", + "first_name": "Admin", + "last_name": "User", + "role": "admin" + } +} + +script:post-response { + function onResponse(res) { + let data = res.getBody(); + return bru.setEnvVar("token", data.token, { persist: true }); + } + onResponse(res); +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Register Admin User + + Creates a new admin user account with role-based restrictions. + + **Method:** POST + + **Endpoint:** /api/auth/register + + **Request Body:** + - `email` (string): Email address + - `username` (string): Username + - `password` (string): Password + - `first_name` (string, optional): First name + - `last_name` (string, optional): Last name + - `role` (string): Must be "admin" + + **Response:** + - `token` (string): JWT token with admin role + - `user` (object): User details + - `id` (string): User ID + - `email` (string): Email + - `username` (string): Username + - `theme` (string): User theme preference + - `first_name` (string, optional): First name + - `last_name` (string, optional): Last name + - `role` (string): User role ("admin") + + **Status Codes:** + - 201: Created + - 400: Invalid input data + - 403: Forbidden - admin creation restrictions apply + - 409: User exists + + **Role Restrictions:** + - **First User**: Anyone can create first admin (auto-assigned) + - **Existing Admins Present**: Only authenticated admins can create new admin accounts + - **Unauthenticated Users**: Cannot create admin accounts if any admin exists + - **Security**: Requires admin authentication for subsequent admin creation + + **Usage Notes:** + - Use this request only when specifically creating admin accounts + - For regular user creation, use "Register User" request + - Admin token will have elevated privileges for administrative operations + } \ No newline at end of file diff --git a/bruno/user/Login User.bru b/bruno/user/auth/Login User.bru similarity index 99% rename from bruno/user/Login User.bru rename to bruno/user/auth/Login User.bru index fa55c3e..6a50099 100644 --- a/bruno/user/Login User.bru +++ b/bruno/user/auth/Login User.bru @@ -1,7 +1,7 @@ meta { name: Login User type: http - seq: 2 + seq: 1 } post { diff --git a/bruno/user/auth/Register User.bru b/bruno/user/auth/Register User.bru new file mode 100644 index 0000000..ea6b996 --- /dev/null +++ b/bruno/user/auth/Register User.bru @@ -0,0 +1,80 @@ +meta { + name: Register User + type: http + seq: 2 +} + +post { + url: {{base_url}}/api/auth/register + body: json + auth: inherit +} + +body:json { + { + "email": "test@example.com", + "username": "testuser", + "password": "password123", + "first_name": "Test", + "last_name": "User" + } +} + +script:post-response { + function onResponse(res) { + let data = res.getBody(); + return bru.setEnvVar("token", data.token, { persist: true }); + } + onResponse(res); +} + +settings { + encodeUrl: true + timeout: 0 +} + +docs { + ## Register User + + Creates a new user account with role-based restrictions. + + **Method:** POST + + **Endpoint:** /api/auth/register + + **Request Body:** + - `email` (string): Email address + - `username` (string): Username + - `password` (string): Password + - `first_name` (string, optional): First name + - `last_name` (string, optional): Last name + - `role` (string): User role ("user" or "admin") + + **Response:** + - `token` (string): JWT token + - `user` (object): User details + - `id` (string): User ID + - `email` (string): Email + - `username` (string): Username + - `theme` (string): User theme preference + - `first_name` (string, optional): First name + - `last_name` (string, optional): Last name + - `role` (string): User role ("user" or "admin") + + **Status Codes:** + - 201: Created + - 400: Invalid input data + - 403: Forbidden - role-based restrictions apply + - 409: User exists + + **Role Restrictions:** + - **First User**: Automatically gets admin role regardless of request + - **Existing Admins Present**: Only authenticated admins can create new admin accounts + - **No Admins Yet**: Anyone can create first admin (auto-assigned) + - **Regular User Creation**: Anyone can create regular user accounts + - **Unauthenticated Users**: Can only create first admin, not subsequent admins + + **Examples:** + - First admin creation: `{"email": "admin@example.com", "username": "admin", "password": "password123", "role": "admin"}` + - Regular user creation: `{"email": "user@example.com", "username": "user", "password": "password123", "role": "user"}` + } diff --git a/bruno/user/Get Profile.bru b/bruno/user/profile/Get Profile.bru similarity index 100% rename from bruno/user/Get Profile.bru rename to bruno/user/profile/Get Profile.bru diff --git a/bruno/user/Update Email.bru b/bruno/user/profile/Update Email.bru similarity index 100% rename from bruno/user/Update Email.bru rename to bruno/user/profile/Update Email.bru diff --git a/bruno/user/Update Password.bru b/bruno/user/profile/Update Password.bru similarity index 100% rename from bruno/user/Update Password.bru rename to bruno/user/profile/Update Password.bru diff --git a/bruno/user/Update Profile.bru b/bruno/user/profile/Update Profile.bru similarity index 100% rename from bruno/user/Update Profile.bru rename to bruno/user/profile/Update Profile.bru diff --git a/bruno/user/Update Theme.bru b/bruno/user/profile/Update Theme.bru similarity index 100% rename from bruno/user/Update Theme.bru rename to bruno/user/profile/Update Theme.bru diff --git a/bruno/user/Update Username.bru b/bruno/user/profile/Update Username.bru similarity index 100% rename from bruno/user/Update Username.bru rename to bruno/user/profile/Update Username.bru diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index 423d5eb..e21e609 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -136,15 +136,25 @@ func (h *AuthHandler) Register(c echo.Context) error { return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to hash password"}) } - // Set role - first user is always admin, otherwise validate requested role + // Check if any admin users already exist + adminExists := false + for _, u := range users { + if u.Role == "admin" { + adminExists = true + break + } + } + + // Set role - first user is always admin, otherwise validate requested role based on existing admins var userRole string if len(users) == 0 { userRole = "admin" // First user is always admin } else { userRole = req.Role if userRole == "" { - userRole = "user" + userRole = "user" // Default to regular user if not specified } + // Validate role for subsequent users if userRole != "user" && userRole != "admin" { if c.Request().Header.Get("HX-Request") == "true" { @@ -152,6 +162,29 @@ func (h *AuthHandler) Register(c echo.Context) error { } return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid role. must be 'user' or 'admin'"}) } + + // Role-based restrictions: only admins can create admin users if any admin already exists + if userRole == "admin" && adminExists { + // Check if current user is admin (requires authentication) + userID := c.Get("user_id") + if userID == nil { + // Not authenticated - cannot create admin user if admins exist + if c.Request().Header.Get("HX-Request") == "true" { + return c.HTML(http.StatusForbidden, `