-
Notifications
You must be signed in to change notification settings - Fork 0
/
tokengen.go
110 lines (95 loc) · 3.67 KB
/
tokengen.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
package commands
import (
"context"
"crypto/rsa"
"fmt"
"io/ioutil"
"log"
"time"
"github.com/dgrijalva/jwt-go/v4"
"github.com/pkg/errors"
"github.com/tullo/service/business/auth"
"github.com/tullo/service/business/data/user"
"github.com/tullo/service/foundation/database"
"github.com/tullo/service/foundation/keystore"
)
// TokenGen generates a JWT for the specified user.
func TokenGen(traceID string, log *log.Logger, cfg database.Config, userID string, privateKeyFile string, algorithm string) error {
if userID == "" || privateKeyFile == "" || algorithm == "" {
fmt.Println("help: tokengen <id> <private_key_file> <algorithm>")
fmt.Println("algorithm: RS256, HS256")
return ErrHelp
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
db, err := database.Connect(ctx, cfg)
if err != nil {
return errors.Wrap(err, "connect database")
}
defer db.Close()
u := user.NewStore(log, db)
// The call to retrieve a user requires an Admin role by the caller.
claims := auth.Claims{
StandardClaims: jwt.StandardClaims{
Subject: userID,
},
Roles: []string{auth.RoleAdmin},
}
user, err := u.QueryByID(ctx, traceID, claims, userID)
if err != nil {
return errors.Wrap(err, "retrieve user")
}
privatePEM, err := ioutil.ReadFile(privateKeyFile)
if err != nil {
return errors.Wrap(err, "reading PEM private key file")
}
privateKey, err := jwt.ParseRSAPrivateKeyFromPEM(privatePEM)
if err != nil {
return errors.Wrap(err, "parsing PEM into private key")
}
// In a production system, a key id (KID) is used to retrieve the correct
// public key to parse a JWT for auth and claims. A key store is provided
// to the auth package for storage and lookup purpose. This id will be
// assigned to the private key just constructed.
keyID := "54bb2165-71e1-41a6-af3e-7da4a0e1e2c1"
// An authenticator maintains the state required to handle JWT processing.
// It requires a keystore to lookup private and public keys based on a key
// id. There is a keystore implementation in the project.
keyPair := map[string]*rsa.PrivateKey{keyID: privateKey}
keyStore := keystore.NewMap(keyPair)
a, err := auth.New(algorithm, keyStore)
if err != nil {
return errors.Wrap(err, "constructing authenticator")
}
// Generating a token requires defining a set of claims. In this applications
// case, we only care about defining the subject and the user in question and
// the roles they have on the database. This token will expire in a year.
//
// iss (issuer): Issuer of the JWT
// sub (subject): Subject of the JWT (the user)
// aud (audience): Recipient for which the JWT is intended
// exp (expiration time): Time after which the JWT expires
// nbf (not before time): Time before which the JWT must not be accepted for processing
// iat (issued at time): Time at which the JWT was issued; can be used to determine age of the JWT
// jti (JWT ID): Unique identifier; can be used to prevent the JWT from being replayed (allows a token to be used only once)
claims = auth.Claims{
StandardClaims: jwt.StandardClaims{
Issuer: "service project",
Subject: user.ID,
Audience: jwt.ClaimStrings{"students"},
ExpiresAt: jwt.At(time.Now().Add(8760 * time.Hour)),
IssuedAt: jwt.Now(),
},
Roles: user.Roles,
}
// This will generate a JWT with the claims embedded in them. The database
// with need to be configured with the information found in the public key
// file to validate these claims. Dgraph does not support key rotate at
// this time.
token, err := a.GenerateToken(keyID, claims)
if err != nil {
return errors.Wrap(err, "generating token")
}
fmt.Printf("-----BEGIN TOKEN-----\n%s\n-----END TOKEN-----\n", token)
return nil
}