Files
gems/opal-task/internal/auth/tokens.go
T
joakim 4eb18388db feat(backend): add OAuth2/JWT authentication support
- Add OAuth2 client for Authentik integration
- Implement JWT token generation and validation
- Add refresh token support with database storage
- Update database schema with oauth_subject, oauth_provider, and refresh_tokens table
- Create auth package with config, jwt, oauth, and token management
- Add OAuth endpoints: /auth/login, /auth/callback, /auth/refresh, /auth/logout
- Update AuthMiddleware to support both JWT and API key authentication
- Add user helper functions for OAuth user creation and retrieval
- Add .env.example with OAuth configuration template

API keys still work for CLI compatibility while JWT tokens support web/mobile clients.
2026-01-06 15:42:03 +01:00

92 lines
1.7 KiB
Go

package auth
import (
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"fmt"
"time"
"git.jnss.me/joakim/opal/internal/engine"
)
func GenerateRefreshToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
func HashToken(token string) string {
hash := sha256.Sum256([]byte(token))
return base64.URLEncoding.EncodeToString(hash[:])
}
func StoreRefreshToken(userID int, token string, expiresIn int) error {
db := engine.GetDB()
if db == nil {
return fmt.Errorf("database not initialized")
}
hash := HashToken(token)
expiresAt := time.Now().Add(time.Duration(expiresIn) * time.Second).Unix()
_, err := db.Exec(`
INSERT INTO refresh_tokens (user_id, token_hash, expires_at, created_at)
VALUES (?, ?, ?, ?)
`, userID, hash, expiresAt, time.Now().Unix())
return err
}
func ValidateRefreshToken(token string) (int, error) {
db := engine.GetDB()
if db == nil {
return 0, fmt.Errorf("database not initialized")
}
hash := HashToken(token)
var userID int
var expiresAt int64
var revoked int
err := db.QueryRow(`
SELECT user_id, expires_at, revoked
FROM refresh_tokens
WHERE token_hash = ?
`, hash).Scan(&userID, &expiresAt, &revoked)
if err != nil {
return 0, err
}
if revoked == 1 {
return 0, fmt.Errorf("token revoked")
}
if time.Now().Unix() > expiresAt {
return 0, fmt.Errorf("token expired")
}
return userID, nil
}
func RevokeRefreshToken(token string) error {
db := engine.GetDB()
if db == nil {
return fmt.Errorf("database not initialized")
}
hash := HashToken(token)
_, err := db.Exec(`
UPDATE refresh_tokens
SET revoked = 1
WHERE token_hash = ?
`, hash)
return err
}