mirror of
https://github.com/wassname/talk.git
synced 2026-07-21 12:51:03 +08:00
feat: improved user creation
This commit is contained in:
@@ -9,6 +9,7 @@ import {
|
||||
GQLUSER_USERNAME_STATUS,
|
||||
} from "talk-server/graph/tenant/schema/__generated__/types";
|
||||
import { ActionCounts } from "talk-server/models/actions";
|
||||
import { FilterQuery } from "talk-server/models/query";
|
||||
import { TenantResource } from "talk-server/models/tenant";
|
||||
|
||||
function collection(db: Db) {
|
||||
@@ -23,7 +24,8 @@ export interface LocalProfile {
|
||||
export interface OIDCProfile {
|
||||
type: "oidc";
|
||||
id: string;
|
||||
provider: string;
|
||||
issuer: string;
|
||||
audience: string;
|
||||
}
|
||||
|
||||
export interface SSOProfile {
|
||||
@@ -62,6 +64,7 @@ export interface User extends TenantResource {
|
||||
username: string | null;
|
||||
displayName?: string;
|
||||
password?: string;
|
||||
avatar?: string;
|
||||
email?: string;
|
||||
email_verified?: boolean;
|
||||
profiles: Profile[];
|
||||
@@ -73,7 +76,7 @@ export interface User extends TenantResource {
|
||||
created_at: Date;
|
||||
}
|
||||
|
||||
export type CreateUserInput = Omit<
|
||||
export type UpsertUserInput = Omit<
|
||||
User,
|
||||
| "id"
|
||||
| "tenant_id"
|
||||
@@ -84,17 +87,20 @@ export type CreateUserInput = Omit<
|
||||
| "created_at"
|
||||
>;
|
||||
|
||||
export async function createUser(
|
||||
export async function upsertUser(
|
||||
db: Db,
|
||||
tenantID: string,
|
||||
input: CreateUserInput
|
||||
input: UpsertUserInput
|
||||
) {
|
||||
const now = new Date();
|
||||
|
||||
// Create a new ID for the user.
|
||||
const id = uuid.v4();
|
||||
|
||||
// default are the properties set by the application when a new user is
|
||||
// created.
|
||||
const defaults: Sub<User, CreateUserInput> = {
|
||||
id: uuid.v4(),
|
||||
const defaults: Sub<User, UpsertUserInput> = {
|
||||
id,
|
||||
tenant_id: tenantID,
|
||||
tokens: [],
|
||||
action_counts: {},
|
||||
@@ -118,15 +124,61 @@ export async function createUser(
|
||||
created_at: now,
|
||||
};
|
||||
|
||||
if (input.password) {
|
||||
// Hash the user's password with bcrypt.
|
||||
input.password = await bcrypt.hash(input.password, 10);
|
||||
}
|
||||
|
||||
// Merge the defaults and the input together.
|
||||
const user: Readonly<User> = merge({}, defaults, input);
|
||||
|
||||
// Insert it into the database.
|
||||
await collection(db).insertOne(user);
|
||||
// Create a query that will utilize a findOneAndUpdate to facilitate an upsert
|
||||
// operation to ensure no user has the same profile and/or email address. If
|
||||
// any user is found to have the same profile as any of the profiles specified
|
||||
// in the new user object, then we should error here.
|
||||
const filter = createUpsertUserFilter(user);
|
||||
|
||||
return user;
|
||||
// Create the upsert/update operation.
|
||||
const update: { $setOnInsert: Readonly<User> } = {
|
||||
$setOnInsert: user,
|
||||
};
|
||||
|
||||
// Insert it into the database. This may throw an error.
|
||||
const result = await collection(db).findOneAndUpdate(filter, update, {
|
||||
// We are using this to create a user, so we need to upsert it.
|
||||
upsert: true,
|
||||
|
||||
// False to return the updated document instead of the original document.
|
||||
// This lets us detect if the document was updated or not.
|
||||
returnOriginal: false,
|
||||
});
|
||||
|
||||
// Check to see if this was a new user that was upserted, or one was found
|
||||
// that matched existing records. We are sure here that the record exists
|
||||
// because we're returning the updated document and performing an upsert
|
||||
// operation.
|
||||
if (result.value!.id !== id) {
|
||||
// TODO: return better error.
|
||||
throw new Error("user already found");
|
||||
}
|
||||
|
||||
return result.value!;
|
||||
}
|
||||
|
||||
const createUpsertUserFilter = (user: Readonly<User>) => {
|
||||
const query: FilterQuery<User> = {
|
||||
// Query by the profiles if the user is being created with one.
|
||||
$or: user.profiles.map(profile => ({ profiles: { $elemMatch: profile } })),
|
||||
};
|
||||
|
||||
if (user.email) {
|
||||
// Query by the email address if the user is being created with one.
|
||||
query.$or.push({ email: user.email });
|
||||
}
|
||||
|
||||
return query;
|
||||
};
|
||||
|
||||
export async function retrieveUser(db: Db, tenantID: string, id: string) {
|
||||
return collection(db).findOne({ id, tenant_id: tenantID });
|
||||
}
|
||||
@@ -178,7 +230,7 @@ export async function updateUserRole(
|
||||
|
||||
export async function verifyUserPassword(user: User, password: string) {
|
||||
if (user.password) {
|
||||
return bcrypt.compare(user.password, password);
|
||||
return bcrypt.compare(password, user.password);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user