Files
htwkalender/services/auth/main.go
2024-11-26 18:18:18 +01:00

170 lines
4.8 KiB
Go

package main
import (
"fmt"
"github.com/coreos/go-oidc"
"github.com/gorilla/mux"
"golang.org/x/oauth2"
"gopkg.in/square/go-jose.v2/json"
"log"
"log/slog"
"net/http"
"net/url"
"strings"
)
var (
baseUrl = "https://keycloak.imn.htwk-leipzig.de/realms/htwkalender-dev" // Keycloak Realm URL
clientID = "htwkalender-dev" // Client ID
clientSecret = "pWUPXSSqszCWrFkoWuvdyZIuP9bshOcf" // Client secret
redirectURL = "http://localhost/callback" // URL to redirect after login
oidcConfig = oauth2.Config{
ClientID: clientID,
ClientSecret: clientSecret,
Endpoint: oauth2.Endpoint{
AuthURL: baseUrl + "/oauth/authorize",
TokenURL: baseUrl + "/oauth/token",
},
RedirectURL: redirectURL,
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
)
func main() {
// Create router
router := mux.NewRouter()
// Public and private routes
router.HandleFunc("/public", publicHandler)
router.HandleFunc("/private", privateHandler)
log.Println("Starting server on :8081")
log.Fatal(http.ListenAndServe(":8081", router))
}
func publicHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "This is public content, no login required!")
}
func privateHandler(w http.ResponseWriter, r *http.Request) {
// Get bearer token from the request header
token, err := getBearerToken(r)
if err != nil {
http.Error(w, "Unauthorized: No token found", http.StatusUnauthorized)
return
}
// Validate the token using GitLab's token introspection
active, err := introspectToken(token)
if err != nil || !active {
slog.Warn("Failed to introspect token", "error", err)
http.Error(w, "Unauthorized: Invalid token", http.StatusUnauthorized)
return
}
// Get user information
userInfo, err := getUserInfo(token)
if err != nil {
slog.Warn("Failed to get user information", "error", err)
http.Error(w, "Unauthorized: Unable to retrieve user information", http.StatusUnauthorized)
return
}
fmt.Fprintf(w, "Hello, %s! Your email is %s", userInfo.Name, userInfo.Email)
}
func getBearerToken(r *http.Request) (string, error) {
authHeader := r.Header.Get("Authorization")
if authHeader == "" {
return "", fmt.Errorf("Authorization header missing")
}
// Extract token from "Bearer <token>"
token := strings.TrimPrefix(authHeader, "Bearer ")
if token == authHeader {
return "", fmt.Errorf("Malformed authorization header")
}
return token, nil
}
func introspectToken(token string) (bool, error) {
introspectionURL := "https://gitlab.dit.htwk-leipzig.de/oauth/introspect"
// Create the request body
data := url.Values{}
data.Set("token", token)
req, err := http.NewRequest("POST", introspectionURL, strings.NewReader(data.Encode()))
if err != nil {
return false, fmt.Errorf("failed to create introspection request: %v", err)
}
// Use client credentials for basic auth
req.SetBasicAuth(clientID, clientSecret)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return false, fmt.Errorf("failed to introspect token: %v", err)
}
defer resp.Body.Close()
// Check the status code
if resp.StatusCode != http.StatusOK {
return false, fmt.Errorf("introspection request failed with status: %s", resp.Status)
}
var introspectionResponse struct {
Active bool `json:"active"`
Error string `json:"error"`
ErrorDescription string `json:"error_description"`
}
if err := json.NewDecoder(resp.Body).Decode(&introspectionResponse); err != nil {
return false, fmt.Errorf("failed to parse introspection response: %v", err)
}
// Log the introspection response for debugging
fmt.Printf("Introspection response: %+v\n", introspectionResponse)
return introspectionResponse.Active, nil
}
// New function to get user information from GitLab
func getUserInfo(token string) (*UserInfo, error) {
userInfoURL := "https://gitlab.dit.htwk-leipzig.de/oauth/userinfo"
req, err := http.NewRequest("GET", userInfoURL, nil)
if err != nil {
return nil, fmt.Errorf("failed to create user info request: %v", err)
}
req.Header.Set("Authorization", "Bearer "+token)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return nil, fmt.Errorf("failed to get user info: %v", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("user info request failed with status: %s", resp.Status)
}
var userInfo UserInfo
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return nil, fmt.Errorf("failed to parse user info response: %v", err)
}
return &userInfo, nil
}
// Struct for user information
type UserInfo struct {
Email string `json:"email"`
Username string `json:"username"`
Name string `json:"name"`
// Add any other fields you need from the user info response
}