Implement JWT authentication and app passkey support

- Add JWT token generation and validation
- Implement bcrypt password hashing
- Create auth service with register/login/refresh/logout
- Add app passkey generation and management
- Implement protected routes and auth middleware
- Add comprehensive tests for new functionality
This commit is contained in:
2026-04-29 11:50:09 +08:00
parent 901a769ee7
commit 3eeb9f6d26
24 changed files with 2063 additions and 36 deletions

93
internal/auth/jwt_test.go Normal file
View File

@@ -0,0 +1,93 @@
package auth
import (
"strings"
"testing"
"time"
)
func TestGenerateAccessToken(t *testing.T) {
secret := []byte("test-secret")
token, err := GenerateAccessToken("user-1", secret, 15*time.Minute)
if err != nil {
t.Fatalf("GenerateAccessToken = %v", err)
}
if token == "" {
t.Fatal("token is empty")
}
if !strings.Contains(token, ".") {
t.Fatal("token does not look like a JWT")
}
}
func TestParseTokenValid(t *testing.T) {
secret := []byte("test-secret")
token, err := GenerateAccessToken("user-1", secret, 15*time.Minute)
if err != nil {
t.Fatalf("GenerateAccessToken = %v", err)
}
claims, err := ParseToken(token, secret)
if err != nil {
t.Fatalf("ParseToken = %v", err)
}
if claims.UserID != "user-1" {
t.Errorf("UserID = %q, want %q", claims.UserID, "user-1")
}
}
func TestParseTokenWrongSecret(t *testing.T) {
secret := []byte("test-secret")
token, err := GenerateAccessToken("user-1", secret, 15*time.Minute)
if err != nil {
t.Fatalf("GenerateAccessToken = %v", err)
}
_, err = ParseToken(token, []byte("wrong-secret"))
if err == nil {
t.Fatal("expected error for wrong secret, got nil")
}
}
func TestParseTokenExpired(t *testing.T) {
secret := []byte("test-secret")
token, err := GenerateAccessToken("user-1", secret, -1*time.Minute)
if err != nil {
t.Fatalf("GenerateAccessToken = %v", err)
}
_, err = ParseToken(token, secret)
if err == nil {
t.Fatal("expected error for expired token, got nil")
}
}
func TestParseTokenInvalidFormat(t *testing.T) {
_, err := ParseToken("not-a-jwt", []byte("secret"))
if err == nil {
t.Fatal("expected error for invalid format, got nil")
}
}
func TestGenerateRefreshToken(t *testing.T) {
secret := []byte("test-secret")
token, err := GenerateRefreshToken("user-1", secret, 7*24*time.Hour)
if err != nil {
t.Fatalf("GenerateRefreshToken = %v", err)
}
if token == "" {
t.Fatal("token is empty")
}
}
func TestTokenUserIDCarried(t *testing.T) {
secret := []byte("test-secret")
token, _ := GenerateAccessToken("alice-42", secret, 15*time.Minute)
claims, err := ParseToken(token, secret)
if err != nil {
t.Fatalf("ParseToken = %v", err)
}
if claims.UserID != "alice-42" {
t.Errorf("UserID = %q, want %q", claims.UserID, "alice-42")
}
}