Skip to content

Commit 932007a

Browse files
committed
SSO/update_on_login: change email address if no account with that new email address already exists
1 parent 27f145d commit 932007a

File tree

7 files changed

+46
-24
lines changed

7 files changed

+46
-24
lines changed

src/packages/database/postgres/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface UpdateAccountInfoAndPassportOpts {
9292
id: string;
9393
profile: any;
9494
passport_profile: any;
95+
email_address?: string;
9596
}
9697

9798
export interface PostgreSQL extends EventEmitter {

src/packages/next/components/account/config/account/name.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ function ConfigureName() {
8585
type="error"
8686
showIcon
8787
/>
88-
)}{" "}
88+
)}
8989
{get.loading ? (
9090
<Loading />
9191
) : (
@@ -131,7 +131,6 @@ function ConfigureName() {
131131
for content you share publicly.
132132
{original.name && (
133133
<>
134-
{" "}
135134
Setting a username provides optional nicer URL's for shared
136135
public documents. Your username can be between 1 and 39
137136
characters, contain upper and lower case letters, numbers, and

src/packages/next/components/misc/save-button.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1-
import { CSSProperties, useEffect, useMemo, useRef, useState } from "react";
2-
import { cloneDeep, debounce, isEqual } from "lodash";
31
import { Alert, Button, Space } from "antd";
4-
import useIsMounted from "lib/hooks/mounted";
2+
import { cloneDeep, debounce, isEqual } from "lodash";
3+
import { CSSProperties, useEffect, useMemo, useRef, useState } from "react";
4+
55
import Loading from "components/share/loading";
66
import api from "lib/api/post";
7+
import useIsMounted from "lib/hooks/mounted";
8+
79
import { Icon } from "@cocalc/frontend/components/icon";
8-
import { SCHEMA } from "@cocalc/util/schema";
910
import { keys } from "@cocalc/util/misc";
11+
import { SCHEMA } from "@cocalc/util/schema";
1012

1113
interface Props {
1214
edited: any;
@@ -119,7 +121,7 @@ export default function SaveButton({
119121

120122
const doSaveDebounced = useMemo(
121123
() => debounce(doSave, debounce_ms),
122-
[onSave]
124+
[onSave],
123125
);
124126

125127
useEffect(() => {

src/packages/server/auth/sso/passport-login.ts

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@ import { REMEMBER_ME_COOKIE_NAME } from "@cocalc/backend/auth/cookie-names";
2626
import base_path from "@cocalc/backend/base-path";
2727
import getLogger from "@cocalc/backend/logger";
2828
import { set_email_address_verified } from "@cocalc/database/postgres/account-queries";
29-
import type { PostgreSQL } from "@cocalc/database/postgres/types";
29+
import type {
30+
PostgreSQL,
31+
UpdateAccountInfoAndPassportOpts,
32+
} from "@cocalc/database/postgres/types";
3033
import {
3134
PassportLoginLocals,
3235
PassportLoginOpts,
@@ -40,6 +43,7 @@ import { createRememberMeCookie } from "@cocalc/server/auth/remember-me";
4043
import { sanitizeID } from "@cocalc/server/auth/sso/sanitize-id";
4144
import { sanitizeProfile } from "@cocalc/server/auth/sso/sanitize-profile";
4245
import { callback2 as cb2 } from "@cocalc/util/async-utils";
46+
import { is_valid_email_address } from "@cocalc/util/misc";
4347
import { HELP_EMAIL } from "@cocalc/util/theme";
4448
import { emailBelongsToDomain, getEmailDomain } from "./check-required-sso";
4549
import { SSO_API_KEY_COOKIE_NAME } from "./consts";
@@ -487,22 +491,37 @@ export class PassportLogin {
487491
if (locals.new_account_created || locals.account_id == null) return;
488492
const L = logger.extend("maybe_update_account_profile").debug;
489493

490-
// if (opts.emails != null) {
491-
// locals.email_address = opts.emails[0];
492-
// }
493-
494-
L(`account exists and we update name of user based on SSO`);
495-
await this.database.update_account_and_passport({
494+
const upd: UpdateAccountInfoAndPassportOpts = {
496495
account_id: locals.account_id,
497496
first_name: opts.first_name,
498497
last_name: opts.last_name,
499498
strategy: opts.strategyName,
500499
id: opts.id,
501500
profile: opts.profile,
502-
// but not the email address, at least for now
503-
// email_address: locals.email_address,
504501
passport_profile: opts.profile,
505-
});
502+
};
503+
504+
if (Array.isArray(opts.emails) && opts.emails.length >= 1) {
505+
locals.email_address = opts.emails[0];
506+
}
507+
508+
// We update the email address, if it does not belong to another account.
509+
// Most likely, this just returns the very same account (hence an account exists).
510+
if (is_valid_email_address(locals.email_address)) {
511+
const existing_account_id = await cb2(this.database.account_exists, {
512+
email_address: locals.email_address,
513+
});
514+
if (!existing_account_id) {
515+
// There is no account with the new email address, hence we can update the email address as well
516+
upd.email_address = locals.email_address;
517+
L(
518+
`No existing account with email address ${locals.email_address} provided by the SSO strategy. Hence we change the email address of account ${locals.account_id} as well.`,
519+
);
520+
}
521+
}
522+
523+
L(`account exists and we update name of user based on SSO`);
524+
await this.database.update_account_and_passport(upd);
506525
}
507526

508527
// There is a special case, where an api_key was requested.

src/packages/server/auth/sso/unlink-strategy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ upstream SSO provider.
1010
*/
1111

1212
import getPool from "@cocalc/database/pool";
13+
import getStrategies from "@cocalc/database/settings/get-sso-strategies";
1314
import { is_valid_uuid_string } from "@cocalc/util/misc";
1415
import { checkRequiredSSO } from "./check-required-sso";
15-
import getStrategies from "@cocalc/database/settings/get-sso-strategies";
1616

1717
// The name should be something like "google-9999601658192", i.e., a key
1818
// of the passports field.
@@ -42,7 +42,7 @@ export default async function unlinkStrategy(opts: Options): Promise<void> {
4242
// if we can't find the strategy, we still let users unlink it – maybe no longer available?
4343
await pool.query(
4444
"UPDATE accounts SET passports = passports - $2 WHERE account_id=$1",
45-
[account_id, name]
45+
[account_id, name],
4646
);
4747
}
4848

@@ -62,7 +62,7 @@ export async function isBlockedUnlinkStrategy(opts: Opts): Promise<boolean> {
6262

6363
const emailQuery = await pool.query(
6464
"SELECT email_address FROM accounts WHERE account_id=$1",
65-
[account_id]
65+
[account_id],
6666
);
6767
const email = emailQuery.rows[0].email_address;
6868
if (email) {

src/packages/server/auth/throttle.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,9 @@ attacks. This is in memory per-backend server, and doesn't touch
44
the database.
55
*/
66

7-
import getStrategies from "@cocalc/database/settings/get-sso-strategies";
87
import LRU from "lru-cache";
8+
9+
import getStrategies from "@cocalc/database/settings/get-sso-strategies";
910
import { checkRequiredSSO } from "./sso/check-required-sso";
1011

1112
const emailShortCache = new LRU<string, number>({
@@ -30,7 +31,7 @@ async function isExclusiveEmail(email: string) {
3031
export async function signInCheck(
3132
email: string,
3233
ip?: string,
33-
auth_token: boolean = false
34+
auth_token: boolean = false,
3435
): Promise<string | undefined> {
3536
if ((emailShortCache.get(email) ?? 0) > 5) {
3637
// A given email address is allowed at most 5 failed login attempts per minute

src/packages/util/misc.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,10 +403,10 @@ const reValidEmail = (function () {
403403
return new RegExp(sValidEmail);
404404
})();
405405

406-
export function is_valid_email_address(email: string): boolean {
406+
export function is_valid_email_address(email?: unknown): boolean {
407407
// From http://stackoverflow.com/questions/46155/validate-email-address-in-javascript
408408
// but converted to Javascript; it's near the middle but claims to be exactly RFC822.
409-
if (reValidEmail.test(email)) {
409+
if (typeof email === "string" && reValidEmail.test(email)) {
410410
return true;
411411
} else {
412412
return false;

0 commit comments

Comments
 (0)