This commit is contained in:
jay brown
2025-04-16 12:04:47 -07:00
parent cb0207a74f
commit d0bcf7b0d2
5 changed files with 47 additions and 196 deletions
-188
View File
@@ -47,17 +47,6 @@ func RegisterRoutes(e *echo.Echo, config auth.ConfigProvider) {
})
})
//fmt.Printf("registering logoutpath: %s \v", config.GetAuthLogoutPath())
//// Register logout route
//e.GET(config.GetAuthLogoutPath(), func(c echo.Context) error {
// return LogoutHandler(c)
//})
//// Default home page handler if requested
//e.GET(config.GetAuthHomePath(), func(c echo.Context) error {
// return HomeHandler(c, config)
//})
// Apply the JWT Auth middleware first
e.Use(JWTAuthMiddleware(config))
@@ -65,183 +54,6 @@ func RegisterRoutes(e *echo.Echo, config auth.ConfigProvider) {
e.Use(TokenValidationMiddleware(config))
}
// HomeHandler implements a simple home page that shows auth status and available endpoints
func HomeHandler(c echo.Context, config auth.ConfigProvider) error {
// Create a list of all registered routes
println("HomeHandler called.")
var endpoints []string
// Add the auth routes
endpoints = append(endpoints, []string{
config.GetAuthLoginPath(),
config.GetAuthCallbackPath(),
config.GetAuthHomePath(),
config.GetAuthLogoutPath(),
}...)
// Add routes from the permission map
for route := range config.GetAuthRoutePermissions() {
// Skip routes that are already in the list
alreadyAdded := false
for _, endpoint := range endpoints {
if route == endpoint {
alreadyAdded = true
break
}
}
if !alreadyAdded {
endpoints = append(endpoints, route)
}
}
// Check if user is authenticated by looking for token in cookie
tokenCookie, err := c.Cookie("auth_token")
isAuthenticated := (err == nil && tokenCookie.Value != "")
// Variables for user info
username := ""
email := ""
var userGroups []string
// If authenticated, try to decode the JWT token to get user info
if isAuthenticated {
// Parse the JWT token without verification (just to extract info for display)
token, _ := jwt.Parse([]byte(tokenCookie.Value), jwt.WithVerify(false))
if token != nil {
claims, _ := token.AsMap(context.Background())
username, _ = claims["cognito:username"].(string)
email, _ = claims["email"].(string)
// Try to extract groups
userGroups, _ = GetUserGroups(claims)
}
}
// Create HTML for the endpoints list
var linksHTML string
baseURL := c.Scheme() + "://" + c.Request().Host
// Create list items for each endpoint
for _, endpoint := range endpoints {
// Skip endpoints with path parameters for direct linking
if strings.Contains(endpoint, ":") {
displayPath := strings.Replace(endpoint, ":id", "{id}", -1)
linksHTML += fmt.Sprintf("<li>%s (requires parameter)</li>\n", displayPath)
} else {
linksHTML += fmt.Sprintf("<li><a href=\"%s%s\">%s</a></li>\n", baseURL, endpoint, endpoint)
}
}
// Create authentication status section
var authStatusHTML string
if isAuthenticated {
authStatusHTML = fmt.Sprintf(`
<div class="auth-status authenticated">
<h2>Authentication Status: Authenticated</h2>
<p><strong>Username:</strong> %s</p>
<p><strong>Email:</strong> %s</p>
<p><strong>Groups:</strong> %s</p>
<p><a href="%s/logout" class="logout-btn">Logout</a></p>
</div>
`, username, email, strings.Join(userGroups, ", "), baseURL)
} else {
authStatusHTML = fmt.Sprintf(`
<div class="auth-status unauthenticated">
<h2>Authentication Status: Not Authenticated</h2>
<p>You are not currently logged in.</p>
<p><a href="%s/login" class="login-btn">Login</a></p>
</div>
`, baseURL)
}
// Create the complete HTML page
html := fmt.Sprintf(`
<!DOCTYPE html>
<html>
<head>
<title>Authentication Home</title>
<style>
body {
font-family: Arial, sans-serif;
line-height: 1.6;
margin: 20px;
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
h1, h2 {
color: #333;
border-bottom: 1px solid #ddd;
padding-bottom: 10px;
}
ul {
margin-top: 20px;
}
li {
margin-bottom: 8px;
}
a {
color: #0066cc;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.note {
background-color: #f8f9fa;
border-left: 4px solid #5bc0de;
padding: 10px 15px;
margin-top: 20px;
font-size: 0.9em;
}
.auth-status {
margin: 20px 0;
padding: 15px;
border-radius: 5px;
}
.authenticated {
background-color: #dff0d8;
border: 1px solid #d6e9c6;
}
.unauthenticated {
background-color: #f2dede;
border: 1px solid #ebccd1;
}
.login-btn, .logout-btn {
display: inline-block;
padding: 8px 16px;
background-color: #0066cc;
color: white;
border-radius: 4px;
text-decoration: none;
margin-top: 10px;
}
.logout-btn {
background-color: #d9534f;
}
</style>
</head>
<body>
<h1>Authentication Home</h1>
%s
<h2>Available Endpoints</h2>
<ul>
%s
</ul>
<div class="note">
<p><strong>Note:</strong> This is a debugging page. Some endpoints require authentication or specific permissions.</p>
</div>
</body>
</html>
`, authStatusHTML, linksHTML)
return c.HTML(http.StatusOK, html)
}
// GetTokenFromRequest extracts the token from the request
func GetTokenFromRequest(c echo.Context) string {
// First try from Authorization header
+4
View File
@@ -31,3 +31,7 @@ The package reads configuration from environment variables:
- `AWS_REGION`: AWS region where your Cognito User Pool is located (us-east-2)
- `DEBUG`: Set to "true" for debug logging
## Route Permissions
Route permissions are hard-coded in the `route_permissions.go` file.
In the future we may
integrate this information into the swagger API document.
+30
View File
@@ -37,6 +37,9 @@ This package uses AWS Cognito for authentication and implements Role-Based Acces
- **Registers Cognito RBAC middleware and routes before registering API handlers.**
- Sets route-level permissions via a map of route to allowed groups.
## Cognito client user group setup
The cognito client must be setup to redirect back to /login-callback for PKCE flow to work.
## Integration Flow
1. **Config Initialization**: Auth config and route permissions are loaded at startup.
@@ -79,3 +82,30 @@ RegisterHandlers(echoRouter, controllers)
- Route permissions are fully customizable.
- Middleware can be extended or replaced for fine-grained control.
- User info is accessible in handlers via helper functions.
## Cognito auth flow (mermaid)
For a live graph of the auto flow go [here.](https://mermaid.live/edit#pako:eNp9VE2P2jAQ_SsjH3pisyQQAlHLimVXvfQDle0eKqTK6wxgAXZqO7sFxH_vOISvhZKDFdvPb968GXvNhM6QpczinwKVwAfJJ4YvRgroy7lxUsicKwf3Rr9ZNOcbvTwfonlFA9zCgxarZS-X94_nwL6eKOn0dmM7VqQ33e6eJYUwgM-PT3A71xOptrhv2iFoH-MIFxEOFRpOez6L37QqxxLNxxdz2z3dElM-n6Oa4JZvz0KRKw0pNAL4gZk0KBw4fSr3ILRaTqFZydS8cNPo1o_ayBXeBUFwKeYhhz1FHMDAaIHWgj8OxhfBui2-Qh0rbAW7VfjizYEB37FfEJgE8JOWQBjMUDnJ5_a_StoBPPO5zErD3uMvKOkcefUmSbrP-FpN68dFvRHkzAsXszt_7BMZdqXMYehjOSPx9V2dz2u5zyek5hh8Hx7K4_QMVdkYu5AfTrguiTiwNY7toWPAVQanMg4mHWunJnnykS3V1uZaWbyWKfXDsyddQinXlnr7UxQzyNEspLWSKK60cEgd0qN8ffkEd4QGWwjfYONizmpsQSxcZnTf155lxAi6wBFL6TfjZjZiI7UhHHmmh0slWOpMgTVmdDGZsnRMLUGzIvdGVC_FDkJX_JfW-yk1h9Pm6_ZxKd-YGpsYH7piRJWh6etCOZZ2yuMsXbO_LA3rSdAMw7DRiOJOUo86cY0tWZokQb3ZbMRRFLWjsBVvamxVxqsH7aSexO1WHLX8X9TZ_AMOB5n7)
```mermaid
sequenceDiagram
participant Browser
participant AppServer as DoczyApiBE
participant Cognito
Browser->>AppServer: 1. GET /login
Note over AppServer: 2. Generate code_verifier<br/>Generate code_challenge
AppServer->>Browser: 3. Redirect to Cognito
Browser->>Cognito: 4. GET /oauth2/authorize?...code_challenge
Note over Cognito: 5. Process auth request
Cognito->>Browser: 6. Cognito Login Page
Browser->>Cognito: 7. User credentials
Note over Cognito: 8. Validate credentials
Cognito->>Browser: 9. Redirect with code
Browser->>AppServer: 10. GET /login-callback?code=...
Note over AppServer: 11. Retrieve code_verifier
AppServer->>Cognito: 12. POST /oauth2/token<br/>code=...&code_verifier=...
Note over Cognito: 13. Validate code and verifier
Cognito->>AppServer: 14. Tokens response
Note over AppServer: 15. Verify tokens<br/>Check permissions
AppServer->>Browser: 16. Authentication successful
```
+2 -8
View File
@@ -189,14 +189,8 @@ func New(ctx context.Context, cfg Config) (*Server, error) {
cfg.SetRouter(e)
// auth start - may move to separate rbac function
routePermissions := map[string][]string{
"/users": {"exporters", "uploaders", "querybuilders"},
"/users/:id": {"exporters", "querybuilders"},
"/orders": {"exporters"},
"/reports/sales": {"exporters", "uploaders", "querybuilders"},
"/settings": {"exporters"},
"/api/inventory/update": {"exporters", "uploaders"},
}
// update these to match the endpoints.
routePermissions := GetRoutePermissions()
cfg.SetAuthRoutePermissions(routePermissions)
cognitoauth.RegisterRoutes(cfg.GetRouter(), cfg)
+11
View File
@@ -0,0 +1,11 @@
package api
func GetRoutePermissions() map[string][]string {
return map[string][]string{
"/client": {"exporters", "uploaders", "querybuilders"},
"/document": {"exporters", "querybuilders"},
"/query": {"exporters", "uploaders", "querybuilders"},
"/export": {"exporters"},
"/settings": {"exporters"},
}
}