forked from textileio/go-textile
-
Notifications
You must be signed in to change notification settings - Fork 0
/
session.go
157 lines (139 loc) · 3.41 KB
/
session.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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package jwt
import (
"encoding/json"
"errors"
"time"
libp2pc "gx/ipfs/QmPvyPwuCgJ7pDmrKDxRtsScJgBaM5h4EpRL2qQJsmXf4n/go-libp2p-crypto"
"gx/ipfs/QmTRhk7cgjUf2gfQ3p2M9KPECNZEW9XUrmHcFCgog4cPgB/go-libp2p-peer"
"gx/ipfs/QmZNkThpqfVXs9GNbexPrfBbXSLNYeKrE7jwFM2oqHbyqN/go-libp2p-protocol"
"github.com/dgrijalva/jwt-go"
"github.com/golang/protobuf/ptypes"
"github.com/segmentio/ksuid"
"github.com/textileio/textile-go/pb"
)
var ErrClaimsInvalid = errors.New("claims invalid")
var ErrNoToken = errors.New("no token found")
var ErrExpired = errors.New("token expired")
var ErrInvalid = errors.New("token invalid")
type TextileClaims struct {
Scope Scope `json:"scopes"`
jwt.StandardClaims
}
type Scope string
const (
Access Scope = "access"
Refresh Scope = "refresh"
)
func NewSession(sk libp2pc.PrivKey, pid peer.ID, proto protocol.ID, duration time.Duration, httpAddr string, swarmAddr []string) (*pb.CafeSession, error) {
issuer, err := peer.IDFromPrivateKey(sk)
if err != nil {
return nil, err
}
id := ksuid.New().String()
// build access token
now := time.Now()
exp := now.Add(duration)
claims := &TextileClaims{
Scope: Access,
StandardClaims: jwt.StandardClaims{
Audience: string(proto),
ExpiresAt: exp.Unix(),
Id: id,
IssuedAt: time.Now().Unix(),
Issuer: issuer.Pretty(),
Subject: pid.Pretty(),
},
}
access, err := jwt.NewWithClaims(SigningMethodEd25519i, claims).SignedString(sk)
if err != nil {
return nil, err
}
// build refresh token
rexp := now.Add(duration * 2)
rclaims := &TextileClaims{
Scope: Refresh,
StandardClaims: jwt.StandardClaims{
Audience: string(proto),
ExpiresAt: rexp.Unix(),
Id: "r" + id,
IssuedAt: time.Now().Unix(),
Issuer: issuer.Pretty(),
Subject: pid.Pretty(),
},
}
refresh, err := jwt.NewWithClaims(SigningMethodEd25519i, rclaims).SignedString(sk)
if err != nil {
return nil, err
}
// build session
pexp, err := ptypes.TimestampProto(exp)
if err != nil {
return nil, err
}
prexp, err := ptypes.TimestampProto(rexp)
if err != nil {
return nil, err
}
return &pb.CafeSession{
Access: access,
Exp: pexp,
Refresh: refresh,
Rexp: prexp,
Subject: pid.Pretty(),
Type: "JWT",
HttpAddr: httpAddr,
SwarmAddrs: swarmAddr,
}, nil
}
func ParseClaims(claims jwt.Claims) (*TextileClaims, error) {
mapClaims, ok := claims.(jwt.MapClaims)
if !ok {
return nil, ErrClaimsInvalid
}
claimsb, err := json.Marshal(mapClaims)
if err != nil {
return nil, ErrClaimsInvalid
}
var tclaims *TextileClaims
if err := json.Unmarshal(claimsb, &tclaims); err != nil {
return nil, ErrClaimsInvalid
}
return tclaims, nil
}
func Validate(tokenString string, keyfunc jwt.Keyfunc, refreshing bool, audience string, subject *string) error {
token, pErr := jwt.Parse(tokenString, keyfunc)
if token == nil {
return ErrNoToken
}
claims, err := ParseClaims(token.Claims)
if err != nil {
return ErrInvalid
}
if pErr != nil {
if !claims.VerifyExpiresAt(time.Now().Unix(), true) {
return ErrExpired
}
return ErrInvalid
}
switch claims.Scope {
case Access:
if refreshing {
return ErrInvalid
}
case Refresh:
if !refreshing {
return ErrInvalid
}
default:
return ErrInvalid
}
// verify owner
if subject != nil && *subject != claims.Subject {
return ErrInvalid
}
// verify protocol
if !claims.VerifyAudience(audience, true) {
return ErrInvalid
}
return nil
}