1package authn
2
3import (
4	"encoding/base64"
5	"encoding/json"
6	"fmt"
7	"time"
8)
9
10const (
11	TimeFormatToken4 = "2006-01-02 15:04:05 MST"
12)
13
14type AuthnToken interface {
15	// Parse from JSON. Required before further usage.
16	FromJSON(data []byte) error
17	// Raw token as obtained from the authentication service.
18	Raw() []byte
19	// Whether the token will expire soon.
20	ShouldRefresh() bool
21}
22
23type AuthnToken4 struct {
24	bytes     []byte
25	Data      string `json:"data"`
26	Signature string `json:"signature"`
27	Key       string `json:"key"`
28	Timestamp time.Time
29}
30
31// Sample token
32// {"protected":"eyJhbGciOiJjb25qdXIub3JnL3Nsb3NpbG8vdjIiLCJraWQiOiI5M2VjNTEwODRmZTM3Zjc3M2I1ODhlNTYyYWVjZGMxMSJ9","payload":"eyJzdWIiOiJhZG1pbiIsImlhdCI6MTUxMDc1MzI1OX0=","signature":"raCufKOf7sKzciZInQTphu1mBbLhAdIJM72ChLB4m5wKWxFnNz_7LawQ9iYEI_we1-tdZtTXoopn_T1qoTplR9_Bo3KkpI5Hj3DB7SmBpR3CSRTnnEwkJ0_aJ8bql5Cbst4i4rSftyEmUqX-FDOqJdAztdi9BUJyLfbeKTW9OGg-QJQzPX1ucB7IpvTFCEjMoO8KUxZpbHj-KpwqAMZRooG4ULBkxp5nSfs-LN27JupU58oRgIfaWASaDmA98O2x6o88MFpxK_M0FeFGuDKewNGrRc8lCOtTQ9cULA080M5CSnruCqu1Qd52r72KIOAfyzNIiBCLTkblz2fZyEkdSKQmZ8J3AakxQE2jyHmMT-eXjfsEIzEt-IRPJIirI3Qm"}
33// https://www.conjur.org/reference/cryptography.html
34type AuthnToken5 struct {
35	bytes     []byte
36	Protected string `json:"protected"`
37	Payload   string `json:"payload"`
38	Signature string `json:"signature"`
39	iat       time.Time
40	exp       *time.Time
41}
42
43func hasField(fields map[string]string, name string) (hasField bool) {
44	_, hasField = fields[name]
45	return
46}
47
48func NewToken(data []byte) (token AuthnToken, err error) {
49	fields := make(map[string]string)
50	if err = json.Unmarshal(data, &fields); err != nil {
51		err = fmt.Errorf("Unable to unmarshal token : %s", err)
52		return
53	}
54
55	if hasField(fields, "protected") && hasField(fields, "payload") && hasField(fields, "signature") {
56		t := &AuthnToken5{}
57		token = t
58	} else if hasField(fields, "data") && hasField(fields, "timestamp") && hasField(fields, "signature") && hasField(fields, "key") {
59		t := &AuthnToken4{}
60		token = t
61	} else {
62		err = fmt.Errorf("Unrecognized token format")
63		return
64	}
65
66	err = token.FromJSON(data)
67
68	return
69}
70
71func (t *AuthnToken5) FromJSON(data []byte) (err error) {
72	t.bytes = data
73
74	err = json.Unmarshal(data, &t)
75	if err != nil {
76		err = fmt.Errorf("Unable to unmarshal v5 access token %s", err)
77		return
78	}
79
80	// Example: {"sub":"admin","iat":1510753259}
81	payloadFields := make(map[string]interface{})
82	var payloadJSON []byte
83	payloadJSON, err = base64.StdEncoding.DecodeString(t.Payload)
84	if err != nil {
85		err = fmt.Errorf("v5 access token field 'payload' is not valid base64")
86		return
87	}
88	err = json.Unmarshal(payloadJSON, &payloadFields)
89	if err != nil {
90		err = fmt.Errorf("Unable to unmarshal v5 access token field 'payload' : %s", err)
91		return
92	}
93
94	iat_v, ok := payloadFields["iat"]
95	if !ok {
96		err = fmt.Errorf("v5 access token field 'payload' does not contain 'iat'")
97		return
98	}
99	iat_f := iat_v.(float64)
100	// In the absence of exp, the token expires at iat+8 minutes
101	t.iat = time.Unix(int64(iat_f), 0)
102
103	exp_v, ok := payloadFields["exp"]
104	if ok {
105		exp_f := exp_v.(float64)
106		exp := time.Unix(int64(exp_f), 0)
107		t.exp = &exp
108		if t.iat.After(*t.exp) {
109			err = fmt.Errorf("v5 access token expired before it was issued")
110			return
111		}
112	}
113
114	return
115}
116
117func (t *AuthnToken4) FromJSON(data []byte) (err error) {
118	err = json.Unmarshal(data, &t)
119	if err != nil {
120		err = fmt.Errorf("Unable to unmarshal v4 access token %s", err)
121	}
122
123	return
124}
125
126func (t *AuthnToken4) UnmarshalJSON(data []byte) (err error) {
127	type Alias AuthnToken4
128	x := &struct {
129		Data      string `json:"data"`
130		Timestamp string `json:"timestamp"`
131		Signature string `json:"signature"`
132		Key       string `json:"key"`
133		*Alias
134	}{
135		Alias: (*Alias)(t),
136	}
137
138	if err = json.Unmarshal(data, &x); err != nil {
139		return
140	}
141
142	t.Timestamp, err = time.Parse(TimeFormatToken4, x.Timestamp)
143	t.bytes = data
144
145	return
146}
147
148func (t *AuthnToken5) Raw() []byte {
149	return t.bytes
150}
151
152func (t *AuthnToken5) ShouldRefresh() bool {
153	if t.exp != nil {
154		// Expire when the token is 85% expired
155		lifespan := t.exp.Sub(t.iat)
156		duration := float32(lifespan) * 0.85
157		return time.Now().After(t.iat.Add(time.Duration(duration)))
158	} else {
159		// Token expires 8 minutes after issue, by default
160		return time.Now().After(t.iat.Add(5 * time.Minute))
161	}
162}
163
164func (t *AuthnToken4) Raw() []byte {
165	return t.bytes
166}
167
168func (t *AuthnToken4) ShouldRefresh() bool {
169	return time.Now().After(t.Timestamp.Add(5 * time.Minute))
170}
171