forked from sideshow/apns2
-
Notifications
You must be signed in to change notification settings - Fork 0
/
token.go
108 lines (98 loc) · 2.5 KB
/
token.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
package token
import (
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"errors"
"io/ioutil"
"sync"
"time"
jwt "github.com/dgrijalva/jwt-go"
)
const (
// TokenTimeout is the period of time in seconds that a token is valid for.
// If the timestamp for token issue is not within the last hour, APNs
// rejects subsequent push messages. This is set to under an hour so that
// we generate a new token before the existing one expires.
TokenTimeout = 3000
)
// Possible errors when parsing a .p8 file.
var (
ErrAuthKeyNotPem = errors.New("token: AuthKey must be a valid .p8 PEM file")
ErrAuthKeyNotECDSA = errors.New("token: AuthKey must be of type ecdsa.PrivateKey")
ErrAuthKeyNil = errors.New("token: AuthKey was nil")
)
// Token represents an Apple Provider Authentication Token (JSON Web Token).
type Token struct {
sync.Mutex
AuthKey *ecdsa.PrivateKey
KeyID string
TeamID string
IssuedAt int64
Bearer string
}
// AuthKeyFromFile loads a .p8 certificate from a local file and returns a
// *ecdsa.PrivateKey.
func AuthKeyFromFile(filename string) (*ecdsa.PrivateKey, error) {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
return AuthKeyFromBytes(bytes)
}
// AuthKeyFromBytes loads a .p8 certificate from an in memory byte array and
// returns an *ecdsa.PrivateKey.
func AuthKeyFromBytes(bytes []byte) (*ecdsa.PrivateKey, error) {
block, _ := pem.Decode(bytes)
if block == nil {
return nil, ErrAuthKeyNotPem
}
key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return nil, err
}
switch pk := key.(type) {
case *ecdsa.PrivateKey:
return pk, nil
default:
return nil, ErrAuthKeyNotECDSA
}
}
// GenerateIfExpired checks to see if the token is about to expire and
// generates a new token.
func (t *Token) GenerateIfExpired() {
t.Lock()
defer t.Unlock()
if t.Expired() {
t.Generate()
}
}
// Expired checks to see if the token has expired.
func (t *Token) Expired() bool {
return time.Now().Unix() >= (t.IssuedAt + TokenTimeout)
}
// Generate creates a new token.
func (t *Token) Generate() (bool, error) {
if t.AuthKey == nil {
return false, ErrAuthKeyNil
}
issuedAt := time.Now().Unix()
jwtToken := &jwt.Token{
Header: map[string]interface{}{
"alg": "ES256",
"kid": t.KeyID,
},
Claims: jwt.MapClaims{
"iss": t.TeamID,
"iat": issuedAt,
},
Method: jwt.SigningMethodES256,
}
bearer, err := jwtToken.SignedString(t.AuthKey)
if err != nil {
return false, err
}
t.IssuedAt = issuedAt
t.Bearer = bearer
return true, nil
}