-
Notifications
You must be signed in to change notification settings - Fork 0
/
notification.go
196 lines (166 loc) · 5.16 KB
/
notification.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package gofidentweb
import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"
)
/**
* Fident Notification handler
**/
const (
// JSONIDKey is the key used for ID
JSONIDKey = "ID"
// JSONUsernameKey is the key used for username
JSONUsernameKey = "Username"
// JSONCreatedKey is the key used for created epoch
JSONCreatedKey = "Created"
// JSONAttributesKey is the key used for identity attributes
JSONAttributesKey = "Attributes"
// JSONSignatureKey is the key used for fident signature
JSONSignatureKey = "Signature"
// JSONTypeKey is the key used for identity type
JSONTypeKey = "Type"
// ConfirmationResponse is the response body from sucessful requests to
ConfirmationResponse = "con"
)
// SignedPayload is a signed fident payload, signature should match data
type SignedPayload struct {
Data string `json:"Data"`
PayloadType int64 `json:"DataType"`
Signature string `json:"Signature"`
}
// UserUpdatePayload is the serialisable structure for user updates
type UserUpdatePayload struct {
ID string `json:"ID"`
Username string `json:"Username"`
Created int64 `json:"Created"`
Attributes userAttributeSlice `json:"Attributes"`
PayloadType int64 `json:"DataType"`
Timestamp int64 `json:"Timestamp"`
}
// UserAttribute is the serialisable structure for user attributes
type UserAttribute struct {
Key string `json:"Key"`
Value string `json:"Value"`
}
var notificationTokenHelper TokenHelper
// SetNotificationTokenHelper sets the token helper used by notification endpoints
func SetNotificationTokenHelper(t TokenHelper) {
notificationTokenHelper = t
}
// GetFirstNameAttribute returns the first name from account detail attributes
func (a *UserUpdatePayload) GetFirstNameAttribute() string {
for _, r := range a.Attributes {
if r.Key == AttributeKeyFirstName {
return r.Value
}
}
return ""
}
// GetLastNameAttribute returns the last name from account detail attributes
func (a *UserUpdatePayload) GetLastNameAttribute() string {
for _, r := range a.Attributes {
if r.Key == AttributeKeyLastName {
return r.Value
}
}
return ""
}
// GetEmailAddress returns email address for account
func (a *UserUpdatePayload) GetEmailAddress() string {
return a.Username
}
type userAttributeSlice []UserAttribute
// NotificationHandler is interface for notification update handler
type NotificationHandler func(update UserUpdatePayload) bool
func (d userAttributeSlice) Len() int { return len(d) }
func (d userAttributeSlice) Swap(i, j int) {
d[i], d[j] = d[j], d[i]
}
func (d userAttributeSlice) Less(i, j int) bool {
si := d[i].Key
sj := d[j].Key
silow := strings.ToLower(si)
sjlow := strings.ToLower(sj)
if silow == sjlow {
return si < sj
}
return silow < sjlow
}
var (
notificationHandler NotificationHandler
notificationKey string
)
// SetNotificationHandler sets reference to your applications fident notification handler
func SetNotificationHandler(handler NotificationHandler) {
notificationHandler = handler
}
// NotificationEndpoint handles notifications from fident for events such as user updates
func NotificationEndpoint(rw http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
http.NotFound(rw, req)
}
p := make([]byte, req.ContentLength)
req.Body.Read(p)
var payload SignedPayload
err := json.Unmarshal(p, &payload)
if err == nil {
if verifyNotification(payload) {
if notificationHandler != nil {
if notificationTokenHelper.aesKey == "" {
fmt.Printf("Fident notification helper: Failed to decrypt payload data (decryption key not set)")
return
}
var pl UserUpdatePayload
dat, err := decryptPayload(notificationTokenHelper.aesKey, payload.Data)
if err != nil {
fmt.Printf("Fident notification helper: Failed to decrypt payload data")
return
}
err = json.Unmarshal([]byte(dat), &pl)
if err != nil {
http.NotFound(rw, req)
}
if notificationHandler(pl) {
rw.Write([]byte(ConfirmationResponse))
}
} else {
fmt.Printf("Fident notification helper: Recieved notification but notificationHandler has not been set")
}
} else {
fmt.Printf("Fident notification helper: Recieved unsigned notification")
}
}
}
// rsaVerify message signature with public key
func rsaVerify(data, signature string, pubkey *rsa.PublicKey) bool {
rawSignature, err := base64.URLEncoding.DecodeString(signature)
if err != nil {
return false
}
hash := sha256.New()
hash.Write([]byte(data))
hashresult := hash.Sum(nil)
result := rsa.VerifyPKCS1v15(pubkey, crypto.SHA256, hashresult, rawSignature)
if result == nil {
return true
}
return false
}
// verifies notification payload originates from Fident
func verifyNotification(n SignedPayload) bool {
return rsaVerify(n.Data, n.Signature, ¬ificationTokenHelper.rsaPublicKey)
}
// NotificationFirstNameAttributeKey returns key for first name attribute
func NotificationFirstNameAttributeKey() string {
return AttributeKeyFirstName
}
// NotificationLastNameAttributeKey returns key for last name attribute
func NotificationLastNameAttributeKey() string {
return AttributeKeyLastName
}