summaryrefslogtreecommitdiff
path: root/shrine/utils/auth/auth.go
blob: 3199918e8770ffc0201b5c171d48817eab8dc1a3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
package auth

import (
	"crypto/hmac"
	"crypto/rand"
	"crypto/sha256"
	"encoding/hex"
	"shrine/config"
	"shrine/models"
	"shrine/repositories"
	"shrine/utils/meta"
	"strings"
	"time"

	"github.com/gofiber/fiber/v2"
)

const (
	userKey      = "__auth_user"
	tokenHashKey = "__auth_token_hash"
)

func GenerateToken() (string, error) {
	bytes := make([]byte, 32)
	_, err := rand.Read(bytes)
	if err != nil {
		return "", err
	}
	return hex.EncodeToString(bytes), nil
}

func HashToken(rawToken string) string {
	mac := hmac.New(sha256.New, []byte(config.Server.Secret))
	mac.Write([]byte(rawToken))
	return hex.EncodeToString(mac.Sum(nil))
}

func IsAuthenticated(context *fiber.Ctx) bool {
	header, ok := meta.Request(context).Header("Authorization")
	if !ok || !strings.HasPrefix(header, "Bearer ") {
		return false
	}

	rawToken := strings.TrimPrefix(header, "Bearer ")
	tokenHash := HashToken(rawToken)

	token, err := repositories.FindValidToken(tokenHash)
	if err != nil {
		return false
	}

	if !token.User.CanAuthenticate() {
		return false
	}

	now := time.Now()
	if token.User.LastSeenAt == nil || time.Since(*token.User.LastSeenAt) > time.Minute {
		token.User.LastSeenAt = &now
		repositories.UpdateLastSeen(&token.User)
	}

	context.Locals(userKey, &token.User)
	context.Locals(tokenHashKey, tokenHash)
	return true
}

func RequireAuthentication(handler fiber.Handler) fiber.Handler {
	return func(context *fiber.Ctx) error {
		if !IsAuthenticated(context) {
			return fiber.ErrUnauthorized
		}
		return handler(context)
	}
}

func GetUser(context *fiber.Ctx) *models.User {
	user, _ := context.Locals(userKey).(*models.User)
	return user
}

func GetTokenHash(context *fiber.Ctx) string {
	hash, _ := context.Locals(tokenHashKey).(string)
	return hash
}

func IssueToken(context *fiber.Ctx, userID uint) (string, error) {
	token, err := GenerateToken()
	if err != nil {
		return "", err
	}

	request := meta.Request(context)
	userAgent, _ := request.Header("User-Agent")

	record := models.Token{
		TokenHash: HashToken(token),
		UserID:    userID,
		ExpiresAt: time.Now().Add(config.Server.TokenExpiry),
		IPAddress: request.IP,
		UserAgent: userAgent,
	}

	if err := repositories.CreateToken(&record); err != nil {
		return "", err
	}

	return token, nil
}