fix(server): user can not signup through oauth if ever invited (#6101)

This commit is contained in:
liuyi
2024-03-13 07:50:10 +00:00
parent fd9084ea6a
commit 573528be41
9 changed files with 58 additions and 13 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "users" ADD COLUMN "registered" BOOLEAN NOT NULL DEFAULT true;

View File

@@ -18,6 +18,9 @@ model User {
createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6) createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz(6)
/// Not available if user signed up through OAuth providers /// Not available if user signed up through OAuth providers
password String? @db.VarChar password String? @db.VarChar
/// Indicate whether the user finished the signup progress.
/// for example, the value will be false if user never registered and invited into a workspace by others.
registered Boolean @default(true)
features UserFeatures[] features UserFeatures[]
customer UserStripeCustomer? customer UserStripeCustomer?

View File

@@ -152,8 +152,9 @@ export class AuthController {
throw new BadRequestException('Invalid Sign-in mail Token'); throw new BadRequestException('Invalid Sign-in mail Token');
} }
const user = await this.user.findOrCreateUser(email, { const user = await this.user.fulfillUser(email, {
emailVerifiedAt: new Date(), emailVerifiedAt: new Date(),
registered: true,
}); });
await this.auth.setCookie(req, res, user); await this.auth.setCookie(req, res, user);

View File

@@ -49,7 +49,7 @@ export const CurrentUser = createParamDecorator(
); );
export interface CurrentUser export interface CurrentUser
extends Omit<User, 'password' | 'createdAt' | 'emailVerifiedAt'> { extends Pick<User, 'id' | 'email' | 'avatarUrl' | 'name'> {
hasPassword: boolean | null; hasPassword: boolean | null;
emailVerified: boolean; emailVerified: boolean;
} }

View File

@@ -36,12 +36,18 @@ export function parseAuthUserSeqNum(value: any) {
} }
export function sessionUser( export function sessionUser(
user: Omit<User, 'password'> & { password?: string | null } user: Pick<
User,
'id' | 'email' | 'avatarUrl' | 'name' | 'emailVerifiedAt'
> & { password?: string | null }
): CurrentUser { ): CurrentUser {
return assign(omit(user, 'password', 'emailVerifiedAt', 'createdAt'), { return assign(
hasPassword: user.password !== null, omit(user, 'password', 'registered', 'emailVerifiedAt', 'createdAt'),
emailVerified: user.emailVerifiedAt !== null, {
}); hasPassword: user.password !== null,
emailVerified: user.emailVerifiedAt !== null,
}
);
} }
@Injectable() @Injectable()

View File

@@ -42,7 +42,9 @@ export class UserManagementResolver {
if (user) { if (user) {
return this.feature.addEarlyAccess(user.id); return this.feature.addEarlyAccess(user.id);
} else { } else {
const user = await this.users.createAnonymousUser(email); const user = await this.users.createAnonymousUser(email, {
registered: false,
});
return this.feature.addEarlyAccess(user.id); return this.feature.addEarlyAccess(user.id);
} }
} }

View File

@@ -11,11 +11,12 @@ export class UserService {
email: true, email: true,
emailVerifiedAt: true, emailVerifiedAt: true,
avatarUrl: true, avatarUrl: true,
registered: true,
} satisfies Prisma.UserSelect; } satisfies Prisma.UserSelect;
constructor(private readonly prisma: PrismaClient) {} constructor(private readonly prisma: PrismaClient) {}
get userCreatingData(): Partial<Prisma.UserCreateInput> { get userCreatingData() {
return { return {
name: 'Unnamed', name: 'Unnamed',
features: { features: {
@@ -106,6 +107,26 @@ export class UserService {
return this.createAnonymousUser(email, data); return this.createAnonymousUser(email, data);
} }
async fulfillUser(
email: string,
data: Partial<
Pick<Prisma.UserCreateInput, 'emailVerifiedAt' | 'registered'>
>
) {
return this.prisma.user.upsert({
select: this.defaultUserSelect,
where: {
email,
},
update: data,
create: {
email,
...this.userCreatingData,
...data,
},
});
}
async deleteUser(id: string) { async deleteUser(id: string) {
return this.prisma.user.delete({ where: { id } }); return this.prisma.user.delete({ where: { id } });
} }

View File

@@ -358,7 +358,9 @@ export class WorkspaceResolver {
// only invite if the user is not already in the workspace // only invite if the user is not already in the workspace
if (originRecord) return originRecord.id; if (originRecord) return originRecord.id;
} else { } else {
target = await this.users.createAnonymousUser(email); target = await this.users.createAnonymousUser(email, {
registered: false,
});
} }
const inviteId = await this.permissions.grant( const inviteId = await this.permissions.grant(

View File

@@ -153,9 +153,17 @@ export class OAuthController {
if (user) { if (user) {
// we can't directly connect the external account with given email in sign in scenario for safety concern. // we can't directly connect the external account with given email in sign in scenario for safety concern.
// let user manually connect in account sessions instead. // let user manually connect in account sessions instead.
throw new BadRequestException( if (user.registered) {
'The account with provided email is not register in the same way.' throw new BadRequestException(
); 'The account with provided email is not register in the same way.'
);
}
await this.user.fulfillUser(externalAccount.email, {
registered: true,
});
return user;
} else { } else {
user = await this.createUserWithConnectedAccount( user = await this.createUserWithConnectedAccount(
provider, provider,