mirror of
https://github.com/get-drexa/drive.git
synced 2026-02-02 19:21:18 +00:00
feat: initial backend scaffolding
migrating away from convex Co-authored-by: Ona <no-reply@ona.com>
This commit is contained in:
91
apps/backend/internal/auth/tokens.go
Normal file
91
apps/backend/internal/auth/tokens.go
Normal file
@@ -0,0 +1,91 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/google/uuid"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
const (
|
||||
accessTokenValidFor = time.Minute * 15
|
||||
refreshTokenByteLength = 32
|
||||
refreshTokenValidFor = time.Hour * 24 * 30
|
||||
)
|
||||
|
||||
type TokenConfig struct {
|
||||
Issuer string
|
||||
Audience string
|
||||
SecretKey []byte
|
||||
}
|
||||
|
||||
type RefreshToken struct {
|
||||
bun.BaseModel `bun:"refresh_tokens"`
|
||||
|
||||
ID uuid.UUID `bun:",pk,type:uuid"`
|
||||
UserID uuid.UUID `bun:"user_id,notnull"`
|
||||
Token []byte `bun:"-"`
|
||||
TokenHash string `bun:"token_hash,notnull"`
|
||||
ExpiresAt time.Time `bun:"expires_at,notnull"`
|
||||
CreatedAt time.Time `bun:"created_at,notnull"`
|
||||
}
|
||||
|
||||
func GenerateAccessToken(user *User, c *TokenConfig) (string, error) {
|
||||
now := time.Now()
|
||||
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.RegisteredClaims{
|
||||
Issuer: c.Issuer,
|
||||
Audience: jwt.ClaimStrings{c.Audience},
|
||||
Subject: user.ID.String(),
|
||||
ExpiresAt: jwt.NewNumericDate(now.Add(accessTokenValidFor)),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
})
|
||||
|
||||
signed, err := token.SignedString(c.SecretKey)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to sign token: %w", err)
|
||||
}
|
||||
|
||||
return signed, nil
|
||||
}
|
||||
|
||||
func GenerateRefreshToken(user *User, c *TokenConfig) (*RefreshToken, error) {
|
||||
now := time.Now()
|
||||
|
||||
buf := make([]byte, refreshTokenByteLength)
|
||||
if _, err := rand.Read(buf); err != nil {
|
||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
||||
}
|
||||
|
||||
id, err := uuid.NewV7()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate refresh token: %w", err)
|
||||
}
|
||||
|
||||
h := sha256.Sum256(buf)
|
||||
hex := hex.EncodeToString(h[:])
|
||||
|
||||
return &RefreshToken{
|
||||
ID: id,
|
||||
UserID: user.ID,
|
||||
Token: buf,
|
||||
TokenHash: hex,
|
||||
ExpiresAt: now.Add(refreshTokenValidFor),
|
||||
CreatedAt: now,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func ParseAccessToken(token string, c *TokenConfig) (*jwt.RegisteredClaims, error) {
|
||||
parsed, err := jwt.ParseWithClaims(token, &jwt.RegisteredClaims{}, func(token *jwt.Token) (any, error) {
|
||||
return c.SecretKey, nil
|
||||
}, jwt.WithIssuer(c.Issuer), jwt.WithExpirationRequired(), jwt.WithAudience(c.Audience))
|
||||
if err != nil {
|
||||
return nil, newInvalidAccessTokenError(err)
|
||||
}
|
||||
return parsed.Claims.(*jwt.RegisteredClaims), nil
|
||||
}
|
||||
Reference in New Issue
Block a user