@@ -1015,8 +1015,11 @@ type oauthLoginParams struct {
1015
1015
AvatarURL string
1016
1016
// Is UsingGroups is true, then the user will be assigned
1017
1017
// to the Groups provided.
1018
- UsingGroups bool
1019
- Groups []string
1018
+ UsingGroups bool
1019
+ Groups []string
1020
+ OauthConversionEnabled bool
1021
+
1022
+ InitAuditRequest func (params * audit.RequestParams ) (* audit.Request [database.OauthMergeState ], func ())
1020
1023
}
1021
1024
1022
1025
type httpError struct {
@@ -1048,6 +1051,15 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1048
1051
user = params .User
1049
1052
link = params .Link
1050
1053
1054
+ // If you do a convert to OIDC and your email does not match, we need to
1055
+ // catch this and not make a new account.
1056
+ if isMergeStateString (params .State .StateString ) {
1057
+ err := api .convertUserToOauth (ctx , r , tx , params )
1058
+ if err != nil {
1059
+ return err
1060
+ }
1061
+ }
1062
+
1051
1063
if user .ID == uuid .Nil && ! params .AllowSignups {
1052
1064
return httpError {
1053
1065
code : http .StatusForbidden ,
@@ -1228,6 +1240,91 @@ func (api *API) oauthLogin(r *http.Request, params oauthLoginParams) (*http.Cook
1228
1240
return cookie , * key , nil
1229
1241
}
1230
1242
1243
+ // convertUserToOauth will convert a user from password base loginType to
1244
+ // an oauth login type. If it fails, it will return a httpError
1245
+ func (api * API ) convertUserToOauth (ctx context.Context , r * http.Request , db database.Store , params oauthLoginParams ) error {
1246
+ user := params .User
1247
+
1248
+ // Trying to convert to OIDC, but the email does not match.
1249
+ // So do not make a new user, just block the request.
1250
+ if user .ID == uuid .Nil {
1251
+ return httpError {
1252
+ code : http .StatusBadRequest ,
1253
+ msg : fmt .Sprintf ("The oidc account with the email %q does not match the email of the account you are trying to convert. Contact your administrator to resolve this issue." , params .Email ),
1254
+ }
1255
+ }
1256
+
1257
+ // nolint:gocritic // Required to auth the oidc convert
1258
+ mergeState , err := db .GetUserOauthMergeState (dbauthz .AsSystemRestricted (ctx ), database.GetUserOauthMergeStateParams {
1259
+ UserID : user .ID ,
1260
+ StateString : params .State .StateString ,
1261
+ })
1262
+ if xerrors .Is (err , sql .ErrNoRows ) {
1263
+ return httpError {
1264
+ code : http .StatusBadRequest ,
1265
+ msg : "No convert login request found with given state. Restart the convert process and try again." ,
1266
+ }
1267
+ }
1268
+ if err != nil {
1269
+ return httpError {
1270
+ code : http .StatusInternalServerError ,
1271
+ msg : err .Error (),
1272
+ }
1273
+ }
1274
+
1275
+ // At this point, this request could be an attempt to convert from
1276
+ // password auth to oauth auth. Always log these attempts.
1277
+ var (
1278
+ auditor = * api .Auditor .Load ()
1279
+ oauthConvertAudit , commitOauthConvertAudit = params .InitAuditRequest (& audit.RequestParams {
1280
+ Audit : auditor ,
1281
+ Log : api .Logger ,
1282
+ Request : r ,
1283
+ Action : database .AuditActionLogin ,
1284
+ })
1285
+ )
1286
+ oauthConvertAudit .Old = mergeState
1287
+ defer commitOauthConvertAudit ()
1288
+
1289
+ // If we do not allow converting to oauth, return an error.
1290
+ if ! params .OauthConversionEnabled {
1291
+ return httpError {
1292
+ code : http .StatusForbidden ,
1293
+ msg : fmt .Sprintf ("Incorrect login type, attempting to use %q but user is of login type %q" ,
1294
+ params .LoginType ,
1295
+ user .LoginType ,
1296
+ ),
1297
+ }
1298
+ }
1299
+
1300
+ // Make sure the merge state generated matches this OIDC login request.
1301
+ // It needs to have the correct login type information for this
1302
+ // user.
1303
+ if user .ID != mergeState .UserID || user .LoginType != mergeState .FromLoginType || params .LoginType != mergeState .ToLoginType {
1304
+ return httpError {
1305
+ code : http .StatusForbidden ,
1306
+ msg : fmt .Sprintf ("Request to convert login type from %s to %s failed" , user .LoginType , params .LoginType ),
1307
+ }
1308
+ }
1309
+
1310
+ // Convert the user and default to the normal login flow.
1311
+ // If the login succeeds, this transaction will commit and the user
1312
+ // will be converted.
1313
+ // nolint:gocritic // system query to update user login type
1314
+ user , err = db .UpdateUserLoginType (dbauthz .AsSystemRestricted (ctx ), database.UpdateUserLoginTypeParams {
1315
+ LoginType : params .LoginType ,
1316
+ UserID : user .ID ,
1317
+ })
1318
+ if err != nil {
1319
+ return httpError {
1320
+ code : http .StatusInternalServerError ,
1321
+ msg : "Failed to convert user to new login type" ,
1322
+ }
1323
+ }
1324
+ return nil
1325
+
1326
+ }
1327
+
1231
1328
// githubLinkedID returns the unique ID for a GitHub user.
1232
1329
func githubLinkedID (u * github.User ) string {
1233
1330
return strconv .FormatInt (u .GetID (), 10 )
@@ -1294,3 +1391,10 @@ func findLinkedUser(ctx context.Context, db database.Store, linkedID string, ema
1294
1391
1295
1392
return user , link , nil
1296
1393
}
1394
+
1395
+ func isMergeStateString (state string ) bool {
1396
+ if strings .HasPrefix (state , mergeStateStringPrefix ) {
1397
+ return true
1398
+ }
1399
+ return false
1400
+ }
0 commit comments