-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathresponse.go
More file actions
368 lines (317 loc) · 10.8 KB
/
Copy pathresponse.go
File metadata and controls
368 lines (317 loc) · 10.8 KB
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
package social
import (
b64 "encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strings"
)
// BasicResponse type
type BasicResponse struct {
}
type errorResponseDetail struct {
Message string `json:"message"`
Property string `json:"property"`
}
// ErrorResponse type
type ErrorResponse struct {
Message string `json:"message"`
Details []errorResponseDetail `json:"details"`
}
// UserProfileResponse type
type UserProfileResponse struct {
UserID string `json:"userId"`
DisplayName string `json:"displayName"`
PictureURL string `json:"pictureUrl"`
StatusMessage string `json:"statusMessage"`
}
type BasicPayload struct {
Iss string `json:"iss"`
Sub string `json:"sub"`
Aud string `json:"aud"`
Exp int `json:"exp"`
Iat int `json:"iat"`
AuthTime int `json:"auth_time"`
Nonce string `json:"nonce"`
Amr []string `json:"amr"`
Name string `json:"name"`
Picture string `json:"picture"`
Email string `json:"email"`
}
// LineProfilePlusPayload https://developers.line.biz/en/docs/partner-docs/line-profile-plus/#id-token
type LineProfilePlusPayload struct {
BasicPayload
GivenName string `json:"given_name"`
GivenNamePronunciation string `json:"given_name_pronunciation"`
MiddleName string `json:"middle_name"`
FamilyName string `json:"family_name"`
FamilyNamePronunciation string `json:"family_name_pronunciation"`
Gender string `json:"gender"`
Birthdate string `json:"birthdate"`
PhoneNumber string `json:"phone_number"`
Address Address `json:"address"`
}
// Address https://developers.line.biz/en/docs/partner-docs/line-profile-plus/#address-object
type Address struct {
PostalCode string `json:"postal_code"`
Region string `json:"region"`
Locality string `json:"locality"`
StreetAddress string `json:"street_address"`
Country string `json:"country"`
}
// Token verification reponse
type TokenVerifyResponse struct {
Scope string `json:"scope"`
ClientID string `json:"client_id"`
ExpiresIn int `json:"expires_in"`
}
// Token refresh type
type TokenRefreshResponse struct {
// TokenType: Bearer
TokenType string `json:"token_type"`
// Scope: Permissions granted by the user. For more information, see Scopes.
//profile: Permission to get the user's profile information.
//openid: Used to retrieve an ID token. For more information, see ID tokens.
//email: Permission to get the user's email address. openid must be specified at the same time. For more information, see ID tokens.
Scope string `json:"scope"`
// AccessToken: Access token. Valid for 30 days.
AccessToken string `json:"access_token"`
// ExpiresIn: Amount of time in seconds until the access token expires
ExpiresIn int `json:"expires_in"`
// RefreshToken: Which token you want to refresh.
//Token used to get a new access token. Valid up until 10 days after the access token expires.
RefreshToken string `json:"refresh_token"`
}
// VerifyIDTokenResponse type
type VerifyIDTokenResponse struct {
Iss string `json:"iss"`
Sub string `json:"sub"`
Aud string `json:"aud"`
Exp int `json:"exp"`
Iat int `json:"iat"`
AuthTime int `json:"auth_time"`
Nonce string `json:"nonce"`
Amr []string `json:"amr"`
Name string `json:"name"`
Picture string `json:"picture"`
Email string `json:"email"`
}
// GetUserProfileResponse type
type GetUserProfileResponse struct {
// UserID: Identifier of the user
UserID string `json:"userId"`
// DisplayName: User's display name
DisplayName string `json:"displayName"`
// PictureURL: Profile image URL. "https" image URL. Not included in the response if the user doesn't have a profile image.
PictureURL string `json:"pictureUrl"`
//StatusMessage: User's status message. Not included in the response if the user doesn't have a status message.
StatusMessage string `json:"statusMessage"`
}
// GetFriendshipStatusResponse type
type GetFriendshipStatusResponse struct {
// FriendFlag: true if the user has added the bot as a friend and has not blocked the bot. Otherwise, false.
FriendFlag bool `json:"friendFlag"`
}
// GetUserInfoResponse type
// OIDC-compliant response from /oauth2/v2.1/userinfo endpoint
// https://developers.line.biz/en/reference/line-login/#userinfo
type GetUserInfoResponse struct {
// Sub: User ID
Sub string `json:"sub"`
// Name: User's display name. Only included if the profile scope was specified.
Name string `json:"name,omitempty"`
// Picture: User's profile image URL. Only included if the profile scope was specified.
Picture string `json:"picture,omitempty"`
}
// TokenResponse type
type TokenResponse struct {
// AccessToken: Access token. Valid for 30 days.
AccessToken string `json:"access_token"`
// ExpiresIn: Amount of time in seconds until the access token expires
ExpiresIn int `json:"expires_in"`
// IDToken: JSON Web Token (JWT) that includes information about the user.
//This field is returned only if openid is specified in the scope. For more information, see ID tokens.
IDToken string `json:"id_token"`
// RefreshToken: Which token you want to refresh.
//Token used to get a new access token. Valid up until 10 days after the access token expires.
RefreshToken string `json:"refresh_token"`
// Scope: Permissions granted by the user. For more information, see Scopes.
//profile: Permission to get the user's profile information.
//openid: Used to retrieve an ID token. For more information, see ID tokens.
//email: Permission to get the user's email address. openid must be specified at the same time. For more information, see ID tokens.
Scope string `json:"scope"`
// TokenType: Bearer
TokenType string `json:"token_type"`
}
// DecodePayload : decode payload result.
func (t TokenResponse) DecodePayload(channelID string) (*BasicPayload, error) {
splitToken := strings.Split(t.IDToken, ".")
if len(splitToken) != 3 {
return nil, fmt.Errorf("idToken size is wrong")
}
payloadSegment := splitToken[1]
decodedPayload, err := b64.RawURLEncoding.DecodeString(payloadSegment)
if err != nil {
return nil, fmt.Errorf("base64url decode error: %w", err)
}
retPayload := &BasicPayload{}
if err := json.Unmarshal(decodedPayload, retPayload); err != nil {
return nil, fmt.Errorf("json unmarshal error: %v", err)
}
// payload verification
if retPayload.Iss != "https://access.line.me" {
return nil, fmt.Errorf("payload verification failed: wrong issuer")
}
if retPayload.Aud != channelID {
return nil, fmt.Errorf("payload verification failed: wrong audience")
}
return retPayload, nil
}
// DecodeLineProfilePlusPayload : decode line profile+ payload result.
// https://developers.line.biz/en/docs/partner-docs/line-profile-plus/#id-token
func (t TokenResponse) DecodeLineProfilePlusPayload(channelID string) (*LineProfilePlusPayload, error) {
splitToken := strings.Split(t.IDToken, ".")
if len(splitToken) < 3 {
return nil, fmt.Errorf("idToken size is wrong")
}
payloadSegment := splitToken[1]
decodedPayload, err := b64.RawURLEncoding.DecodeString(payloadSegment)
if err != nil {
return nil, fmt.Errorf("base64url decode error: %w", err)
}
retPayload := &LineProfilePlusPayload{}
if err := json.Unmarshal(decodedPayload, retPayload); err != nil {
return nil, fmt.Errorf("json unmarshal error: %w", err)
}
if retPayload.Iss != "https://access.line.me" {
return nil, fmt.Errorf("payload verification failed: wrong issuer")
}
if retPayload.Aud != channelID {
return nil, fmt.Errorf("payload verification failed: wrong audience")
}
return retPayload, nil
}
func checkResponse(res *http.Response) error {
if res.StatusCode != http.StatusOK {
decoder := json.NewDecoder(res.Body)
result := ErrorResponse{}
if err := decoder.Decode(&result); err != nil {
return &APIError{
Code: res.StatusCode,
}
}
return &APIError{
Code: res.StatusCode,
Response: &result,
}
}
return nil
}
func checkResponseNoContent(res *http.Response) error {
if res.StatusCode != http.StatusNoContent {
decoder := json.NewDecoder(res.Body)
result := ErrorResponse{}
if err := decoder.Decode(&result); err != nil {
return &APIError{
Code: res.StatusCode,
}
}
return &APIError{
Code: res.StatusCode,
Response: &result,
}
}
return nil
}
func decodeToBasicResponse(res *http.Response) (*BasicResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := BasicResponse{}
if err := decoder.Decode(&result); err != nil {
if err == io.EOF {
return &result, nil
}
return nil, err
}
return &result, nil
}
func decodeToTokenResponse(res *http.Response) (*TokenResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := TokenResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToTokenVerifyResponse(res *http.Response) (*TokenVerifyResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := TokenVerifyResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToTokenRefreshResponse(res *http.Response) (*TokenRefreshResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := TokenRefreshResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToVerifyIDTokenResponse(res *http.Response) (*VerifyIDTokenResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := VerifyIDTokenResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToGetUserProfileResponse(res *http.Response) (*GetUserProfileResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := GetUserProfileResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToGetFriendshipStatusResponse(res *http.Response) (*GetFriendshipStatusResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := GetFriendshipStatusResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}
func decodeToGetUserInfoResponse(res *http.Response) (*GetUserInfoResponse, error) {
if err := checkResponse(res); err != nil {
return nil, err
}
decoder := json.NewDecoder(res.Body)
result := GetUserInfoResponse{}
if err := decoder.Decode(&result); err != nil {
return nil, err
}
return &result, nil
}