@@ -204,12 +204,10 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
204
204
return
205
205
}
206
206
207
- var claims struct {
208
- Email string `json:"email"`
209
- Verified bool `json:"email_verified"`
210
- Username string `json:"preferred_username"`
211
- Picture string `json:"picture"`
212
- }
207
+ // "email_verified" is an optional claim that changes the behavior
208
+ // of our OIDC handler, so each property must be pulled manually out
209
+ // of the claim mapping.
210
+ claims := map [string ]interface {}{}
213
211
err = idToken .Claims (& claims )
214
212
if err != nil {
215
213
httpapi .Write (rw , http .StatusInternalServerError , codersdk.Response {
@@ -218,47 +216,69 @@ func (api *API) userOIDC(rw http.ResponseWriter, r *http.Request) {
218
216
})
219
217
return
220
218
}
221
- if claims .Email == "" {
219
+ emailRaw , ok := claims ["email" ]
220
+ if ! ok {
222
221
httpapi .Write (rw , http .StatusBadRequest , codersdk.Response {
223
222
Message : "No email found in OIDC payload!" ,
224
223
})
225
224
return
226
225
}
227
- if ! claims .Verified {
228
- httpapi .Write (rw , http .StatusForbidden , codersdk.Response {
229
- Message : fmt .Sprintf ("Verify the %q email address on your OIDC provider to authenticate!" , claims .Email ),
226
+ email , ok := emailRaw .(string )
227
+ if ! ok {
228
+ httpapi .Write (rw , http .StatusBadRequest , codersdk.Response {
229
+ Message : fmt .Sprintf ("Email in OIDC payload isn't a string. Got: %t" , emailRaw ),
230
230
})
231
231
return
232
232
}
233
+ verifiedRaw , ok := claims ["email_verified" ]
234
+ if ok {
235
+ verified , ok := verifiedRaw .(bool )
236
+ if ok && ! verified {
237
+ httpapi .Write (rw , http .StatusForbidden , codersdk.Response {
238
+ Message : fmt .Sprintf ("Verify the %q email address on your OIDC provider to authenticate!" , email ),
239
+ })
240
+ return
241
+ }
242
+ }
243
+ usernameRaw , ok := claims ["preferred_username" ]
244
+ var username string
245
+ if ok {
246
+ username , _ = usernameRaw .(string )
247
+ }
233
248
// The username is a required property in Coder. We make a best-effort
234
249
// attempt at using what the claims provide, but if that fails we will
235
250
// generate a random username.
236
- if ! httpapi .UsernameValid (claims . Username ) {
251
+ if ! httpapi .UsernameValid (username ) {
237
252
// If no username is provided, we can default to use the email address.
238
253
// This will be converted in the from function below, so it's safe
239
254
// to keep the domain.
240
- if claims . Username == "" {
241
- claims . Username = claims . Email
255
+ if username == "" {
256
+ username = email
242
257
}
243
- claims . Username = httpapi .UsernameFrom (claims . Username )
258
+ username = httpapi .UsernameFrom (username )
244
259
}
245
260
if api .OIDCConfig .EmailDomain != "" {
246
- if ! strings .HasSuffix (claims . Email , api .OIDCConfig .EmailDomain ) {
261
+ if ! strings .HasSuffix (email , api .OIDCConfig .EmailDomain ) {
247
262
httpapi .Write (rw , http .StatusForbidden , codersdk.Response {
248
- Message : fmt .Sprintf ("Your email %q is not a part of the %q domain!" , claims . Email , api .OIDCConfig .EmailDomain ),
263
+ Message : fmt .Sprintf ("Your email %q is not a part of the %q domain!" , email , api .OIDCConfig .EmailDomain ),
249
264
})
250
265
return
251
266
}
252
267
}
268
+ var picture string
269
+ pictureRaw , ok := claims ["picture" ]
270
+ if ok {
271
+ picture , _ = pictureRaw .(string )
272
+ }
253
273
254
274
cookie , err := api .oauthLogin (r , oauthLoginParams {
255
275
State : state ,
256
276
LinkedID : oidcLinkedID (idToken ),
257
277
LoginType : database .LoginTypeOIDC ,
258
278
AllowSignups : api .OIDCConfig .AllowSignups ,
259
- Email : claims . Email ,
260
- Username : claims . Username ,
261
- AvatarURL : claims . Picture ,
279
+ Email : email ,
280
+ Username : username ,
281
+ AvatarURL : picture ,
262
282
})
263
283
var httpErr httpError
264
284
if xerrors .As (err , & httpErr ) {
0 commit comments