/
console_authenticate.go
133 lines (119 loc) · 4.14 KB
/
console_authenticate.go
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
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Copyright 2019 The Nakama Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package server
import (
"context"
"crypto"
"database/sql"
"errors"
"fmt"
"time"
jwt "github.com/golang-jwt/jwt/v4"
"github.com/heroiclabs/nakama/v3/console"
"github.com/jackc/pgtype"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type ConsoleTokenClaims struct {
Username string `json:"usn,omitempty"`
Email string `json:"ema,omitempty"`
Role console.UserRole `json:"rol,omitempty"`
ExpiresAt int64 `json:"exp,omitempty"`
Cookie string `json:"cki,omitempty"`
}
func (stc *ConsoleTokenClaims) Valid() error {
// Verify expiry.
if stc.ExpiresAt <= time.Now().UTC().Unix() {
vErr := new(jwt.ValidationError)
vErr.Inner = errors.New("Token is expired")
vErr.Errors |= jwt.ValidationErrorExpired
return vErr
}
return nil
}
func parseConsoleToken(hmacSecretByte []byte, tokenString string) (username, email string, role console.UserRole, exp int64, ok bool) {
token, err := jwt.ParseWithClaims(tokenString, &ConsoleTokenClaims{}, func(token *jwt.Token) (interface{}, error) {
if s, ok := token.Method.(*jwt.SigningMethodHMAC); !ok || s.Hash != crypto.SHA256 {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return hmacSecretByte, nil
})
if err != nil {
return
}
claims, ok := token.Claims.(*ConsoleTokenClaims)
if !ok || !token.Valid {
return
}
return claims.Username, claims.Email, claims.Role, claims.ExpiresAt, true
}
func (s *ConsoleServer) Authenticate(ctx context.Context, in *console.AuthenticateRequest) (*console.ConsoleSession, error) {
role := console.UserRole_USER_ROLE_UNKNOWN
var uname string
var email string
switch in.Username {
case s.config.GetConsole().Username:
if in.Password == s.config.GetConsole().Password {
role = console.UserRole_USER_ROLE_ADMIN
uname = in.Username
}
default:
var err error
uname, email, role, err = s.lookupConsoleUser(ctx, in.Username, in.Password)
if err != nil {
return nil, err
}
}
if role == console.UserRole_USER_ROLE_UNKNOWN {
return nil, status.Error(codes.Unauthenticated, "Invalid credentials.")
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, &ConsoleTokenClaims{
ExpiresAt: time.Now().UTC().Add(time.Duration(s.config.GetConsole().TokenExpirySec) * time.Second).Unix(),
Username: uname,
Email: email,
Role: role,
Cookie: s.cookie,
})
key := []byte(s.config.GetConsole().SigningKey)
signedToken, _ := token.SignedString(key)
return &console.ConsoleSession{Token: signedToken}, nil
}
func (s *ConsoleServer) lookupConsoleUser(ctx context.Context, unameOrEmail, password string) (uname string, email string, role console.UserRole, err error) {
role = console.UserRole_USER_ROLE_UNKNOWN
query := "SELECT username, email, role, password, disable_time FROM console_user WHERE username = $1 OR email = $1"
var dbPassword []byte
var dbDisableTime pgtype.Timestamptz
err = s.db.QueryRowContext(ctx, query, unameOrEmail).Scan(&uname, &email, &role, &dbPassword, &dbDisableTime)
if err != nil {
if err == sql.ErrNoRows {
err = nil
}
return
}
// Check if it's disabled.
if dbDisableTime.Status == pgtype.Present && dbDisableTime.Time.Unix() != 0 {
s.logger.Info("Console user account is disabled.", zap.String("username", unameOrEmail))
err = status.Error(codes.PermissionDenied, "Invalid credentials.")
return
}
// Check password
err = bcrypt.CompareHashAndPassword(dbPassword, []byte(password))
if err != nil {
err = status.Error(codes.Unauthenticated, "Invalid credentials.")
return
}
return
}