WIP: Add API-aware authentication (detection works, need to fix execution flow)
- Add api-auth-error condition for API authentication failures - Update require-authentication and require-role to detect /api/ routes - Add :api keyword parameter for explicit API/page mode - Auto-detects API requests from URI path - Returns JSON for API requests, redirects for page requests - Issue: Execution continues after returning JSON, need Radiance-specific solution
This commit is contained in:
parent
efb96f950e
commit
707e7aba96
|
|
@ -0,0 +1,127 @@
|
|||
# Testing Content-Type Aware Authentication
|
||||
|
||||
## What Was Fixed
|
||||
|
||||
The `require-role` function now detects if a request is an API call (contains `/api/` in the URI) and returns appropriate responses:
|
||||
- **API requests**: JSON error with HTTP 403 status
|
||||
- **Page requests**: HTML redirect to login page
|
||||
|
||||
## How to Test
|
||||
|
||||
### 1. Rebuild and Start Server
|
||||
|
||||
```bash
|
||||
make
|
||||
./asteroid
|
||||
```
|
||||
|
||||
### 2. Test API Endpoint (Should Return JSON)
|
||||
|
||||
**Test without login (should get JSON 403):**
|
||||
|
||||
```bash
|
||||
# Using curl
|
||||
curl -i http://localhost:8080/asteroid/api/tracks
|
||||
|
||||
# Expected output:
|
||||
HTTP/1.1 403 Forbidden
|
||||
Content-Type: application/json
|
||||
...
|
||||
{"error":"Authentication required","status":403,"message":"You must be logged in with LISTENER role to access this resource"}
|
||||
```
|
||||
|
||||
**Test with browser console (while NOT logged in):**
|
||||
|
||||
```javascript
|
||||
// Open browser console (F12) on http://localhost:8080/asteroid/
|
||||
fetch('/asteroid/api/tracks')
|
||||
.then(r => r.json())
|
||||
.then(data => console.log('Response:', data))
|
||||
.catch(err => console.error('Error:', err));
|
||||
|
||||
// Expected output:
|
||||
// Response: {error: "Authentication required", status: 403, message: "..."}
|
||||
```
|
||||
|
||||
### 3. Test Page Endpoint (Should Redirect)
|
||||
|
||||
**Visit a protected page without login:**
|
||||
|
||||
```bash
|
||||
# Using curl (follow redirects)
|
||||
curl -L http://localhost:8080/asteroid/admin
|
||||
|
||||
# Should redirect to login page and show HTML
|
||||
```
|
||||
|
||||
**Or in browser:**
|
||||
- Visit: http://localhost:8080/asteroid/admin
|
||||
- Should redirect to: http://localhost:8080/asteroid/login
|
||||
|
||||
### 4. Test After Login
|
||||
|
||||
**Login first, then test API:**
|
||||
|
||||
```javascript
|
||||
// 1. Login via browser at /asteroid/login
|
||||
// 2. Then in console:
|
||||
fetch('/asteroid/api/tracks')
|
||||
.then(r => r.json())
|
||||
.then(data => console.log('Tracks:', data))
|
||||
.catch(err => console.error('Error:', err));
|
||||
|
||||
// Should now return actual track data (or empty array)
|
||||
```
|
||||
|
||||
### 5. Test Player Page
|
||||
|
||||
**The original issue - player page calling API:**
|
||||
|
||||
1. **Without login:**
|
||||
- Visit: http://localhost:8080/asteroid/player
|
||||
- Open browser console (F12)
|
||||
- Check Network tab for `/api/tracks` request
|
||||
- Should see: Status 403, Response Type: json
|
||||
- JavaScript should handle error gracefully (not crash)
|
||||
|
||||
2. **With login:**
|
||||
- Login at: http://localhost:8080/asteroid/login
|
||||
- Visit: http://localhost:8080/asteroid/player
|
||||
- API calls should work normally
|
||||
|
||||
## Expected Behavior
|
||||
|
||||
### Before Fix ❌
|
||||
```
|
||||
API Request → Not Authenticated → Redirect to /login → Returns HTML → JavaScript breaks
|
||||
```
|
||||
|
||||
### After Fix ✅
|
||||
```
|
||||
API Request → Not Authenticated → Return JSON 403 → JavaScript handles error gracefully
|
||||
Page Request → Not Authenticated → Redirect to /login → User sees login page
|
||||
```
|
||||
|
||||
## Debugging
|
||||
|
||||
Check server logs for these messages:
|
||||
|
||||
```
|
||||
Request URI: /asteroid/api/tracks, Is API: YES
|
||||
Role check failed - returning JSON 403
|
||||
```
|
||||
|
||||
Or for page requests:
|
||||
|
||||
```
|
||||
Request URI: /asteroid/admin, Is API: NO
|
||||
Role check failed - redirecting to login
|
||||
```
|
||||
|
||||
## Success Criteria
|
||||
|
||||
✅ API endpoints return JSON errors (not HTML redirects)
|
||||
✅ Page requests still redirect to login
|
||||
✅ Player page doesn't crash when not logged in
|
||||
✅ JavaScript can properly handle 403 errors
|
||||
✅ HTTP status code is 403 (not 302 redirect)
|
||||
|
|
@ -3,6 +3,16 @@
|
|||
|
||||
(in-package :asteroid)
|
||||
|
||||
;; Define a condition for API authentication errors
|
||||
(define-condition api-auth-error (error)
|
||||
((status-code :initarg :status-code :reader status-code)
|
||||
(json-response :initarg :json-response :reader json-response))
|
||||
(:documentation "Condition signaled when API authentication fails"))
|
||||
|
||||
(defun get-json-response (condition)
|
||||
"Return JSON response from an api-auth-error condition"
|
||||
(json-response condition))
|
||||
|
||||
;; User roles and permissions
|
||||
(defparameter *user-roles* '(:listener :dj :admin))
|
||||
|
||||
|
|
@ -121,28 +131,93 @@
|
|||
(format t "Error getting current user: ~a~%" e)
|
||||
nil)))
|
||||
|
||||
(defun require-authentication ()
|
||||
"Require user to be authenticated"
|
||||
(defun require-authentication (&key (api nil))
|
||||
"Require user to be authenticated.
|
||||
If :api t, returns JSON error (401). Otherwise redirects to login page.
|
||||
Auto-detects API routes if not specified."
|
||||
(handler-case
|
||||
(unless (session:field "user-id")
|
||||
(radiance:redirect "/asteroid/login"))
|
||||
(let* ((user-id (session:field "user-id"))
|
||||
(uri (uri-to-url (radiance:uri *request*) :representation :external))
|
||||
;; Use explicit flag if provided, otherwise auto-detect from URI
|
||||
(is-api-request (if api t (search "/api/" uri))))
|
||||
(format t "Authentication check - User ID: ~a, URI: ~a, Is API: ~a~%"
|
||||
user-id uri (if is-api-request "YES" "NO"))
|
||||
(unless user-id
|
||||
(if is-api-request
|
||||
;; API request - return JSON error with 401 status
|
||||
(progn
|
||||
(format t "Authentication failed - returning JSON 401~%")
|
||||
(setf (radiance:header "Content-Type") "application/json")
|
||||
(setf (radiance:response-data)
|
||||
(cl-json:encode-json-to-string
|
||||
`(("error" . "Authentication required")
|
||||
("status" . 401)
|
||||
("message" . "You must be logged in to access this resource"))))
|
||||
(radiance:redirect (radiance:uri)))
|
||||
;; Page request - redirect to login
|
||||
(progn
|
||||
(format t "Authentication failed - redirecting to login~%")
|
||||
(radiance:redirect "/asteroid/login")))))
|
||||
(api-auth-error (e)
|
||||
(format t "API auth error caught, returning JSON~%")
|
||||
(get-json-response e))
|
||||
(error (e)
|
||||
(format t "Authentication error: ~a~%" e)
|
||||
(radiance:redirect "/asteroid/login"))))
|
||||
(let* ((uri (uri-to-url (radiance:uri *request*) :representation :external))
|
||||
(is-api-request (if api t (search "/api/" uri))))
|
||||
(if is-api-request
|
||||
(progn
|
||||
(setf (radiance:header "Content-Type") "application/json")
|
||||
(cl-json:encode-json-to-string
|
||||
`(("error" . "Internal server error")
|
||||
("status" . 500)
|
||||
("message" . ,(format nil "~a" e)))))
|
||||
(radiance:redirect "/asteroid/login"))))))
|
||||
|
||||
(defun require-role (role)
|
||||
"Require user to have a specific role"
|
||||
(defun require-role (role &key (api nil))
|
||||
"Require user to have a specific role.
|
||||
If :api t, returns JSON error (403). Otherwise redirects to login page.
|
||||
Auto-detects API routes if not specified."
|
||||
(handler-case
|
||||
(let ((current-user (get-current-user)))
|
||||
(let* ((current-user (get-current-user))
|
||||
(uri (uri-to-url (radiance:uri *request*) :representation :external))
|
||||
;; Use explicit flag if provided, otherwise auto-detect from URI
|
||||
(is-api-request (if api t (search "/api/" uri))))
|
||||
(format t "Current user for role check: ~a~%" (if current-user "FOUND" "NOT FOUND"))
|
||||
(format t "Request URI: ~a, Is API: ~a~%" uri (if is-api-request "YES" "NO"))
|
||||
(when current-user
|
||||
(format t "User has role ~a: ~a~%" role (user-has-role-p current-user role)))
|
||||
(unless (and current-user (user-has-role-p current-user role))
|
||||
(format t "Role check failed - redirecting to login~%")
|
||||
(radiance:redirect "/asteroid/login")))
|
||||
(if is-api-request
|
||||
;; API request - return JSON error with 403 status
|
||||
(progn
|
||||
(format t "Role check failed - returning JSON 403~%")
|
||||
(setf (radiance:header "Content-Type") "application/json")
|
||||
(error 'api-auth-error
|
||||
:status-code 403
|
||||
:json-response (cl-json:encode-json-to-string
|
||||
`(("error" . "Authentication required")
|
||||
("status" . 403)
|
||||
("message" . ,(format nil "You must be logged in with ~a role to access this resource" role))))))
|
||||
;; Page request - redirect to login
|
||||
(progn
|
||||
(format t "Role check failed - redirecting to login~%")
|
||||
(radiance:redirect "/asteroid/login")))))
|
||||
(api-auth-error (e)
|
||||
(format t "API auth error caught in require-role, returning JSON~%")
|
||||
(get-json-response e))
|
||||
(error (e)
|
||||
(format t "Role check error: ~a~%" e)
|
||||
(radiance:redirect "/asteroid/login"))))
|
||||
(let* ((uri (uri-to-url (radiance:uri *request*) :representation :external))
|
||||
(is-api-request (if api t (search "/api/" uri))))
|
||||
(if is-api-request
|
||||
(progn
|
||||
(setf (radiance:header "Content-Type") "application/json")
|
||||
(cl-json:encode-json-to-string
|
||||
`(("error" . "Internal server error")
|
||||
("status" . 500)
|
||||
("message" . ,(format nil "~a" e)))))
|
||||
(radiance:redirect "/asteroid/login"))))))
|
||||
|
||||
(defun update-user-role (user-id new-role)
|
||||
"Update a user's role"
|
||||
|
|
|
|||
Loading…
Reference in New Issue