chore: Move to Typescript (#2783)
This PR moves the entire project to Typescript. Due to the ~1000 ignores this will lead to a messy codebase for a while, but the churn is worth it – all of those ignore comments are places that were never type-safe previously. closes #1282
This commit is contained in:
188
server/commands/userCreator.ts
Normal file
188
server/commands/userCreator.ts
Normal file
@@ -0,0 +1,188 @@
|
||||
import Sequelize from "sequelize";
|
||||
import { Event, Team, User, UserAuthentication } from "@server/models";
|
||||
import { sequelize } from "../sequelize";
|
||||
|
||||
const Op = Sequelize.Op;
|
||||
type UserCreatorResult = {
|
||||
// @ts-expect-error ts-migrate(2749) FIXME: 'User' refers to a value, but is being used as a t... Remove this comment to see the full error message
|
||||
user: User;
|
||||
isNewUser: boolean;
|
||||
// @ts-expect-error ts-migrate(2749) FIXME: 'UserAuthentication' refers to a value, but is bei... Remove this comment to see the full error message
|
||||
authentication: UserAuthentication;
|
||||
};
|
||||
|
||||
export default async function userCreator({
|
||||
name,
|
||||
email,
|
||||
username,
|
||||
isAdmin,
|
||||
avatarUrl,
|
||||
teamId,
|
||||
authentication,
|
||||
ip,
|
||||
}: {
|
||||
name: string;
|
||||
email: string;
|
||||
username?: string;
|
||||
isAdmin?: boolean;
|
||||
avatarUrl?: string;
|
||||
teamId: string;
|
||||
ip: string;
|
||||
authentication: {
|
||||
authenticationProviderId: string;
|
||||
providerId: string;
|
||||
scopes: string[];
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
};
|
||||
}): Promise<UserCreatorResult> {
|
||||
const { authenticationProviderId, providerId, ...rest } = authentication;
|
||||
const auth = await UserAuthentication.findOne({
|
||||
where: {
|
||||
providerId,
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: User,
|
||||
as: "user",
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// Someone has signed in with this authentication before, we just
|
||||
// want to update the details instead of creating a new record
|
||||
if (auth) {
|
||||
const { user } = auth;
|
||||
|
||||
// We found an authentication record that matches the user id, but it's
|
||||
// associated with a different authentication provider, (eg a different
|
||||
// hosted google domain). This is possible in Google Auth when moving domains.
|
||||
// In the future we may auto-migrate these.
|
||||
if (auth.authenticationProviderId !== authenticationProviderId) {
|
||||
throw new Error(
|
||||
`User authentication ${providerId} already exists for ${auth.authenticationProviderId}, tried to assign to ${authenticationProviderId}`
|
||||
);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
await user.update({
|
||||
email,
|
||||
username,
|
||||
});
|
||||
await auth.update(rest);
|
||||
return {
|
||||
user,
|
||||
authentication: auth,
|
||||
isNewUser: false,
|
||||
};
|
||||
}
|
||||
|
||||
// We found an authentication record, but the associated user was deleted or
|
||||
// otherwise didn't exist. Cleanup the auth record and proceed with creating
|
||||
// a new user. See: https://github.com/outline/outline/issues/2022
|
||||
await auth.destroy();
|
||||
}
|
||||
|
||||
// A `user` record might exist in the form of an invite even if there is no
|
||||
// existing authentication record that matches. In Outline an invite is a
|
||||
// shell user record.
|
||||
const invite = await User.findOne({
|
||||
where: {
|
||||
email,
|
||||
teamId,
|
||||
lastActiveAt: {
|
||||
[Op.eq]: null,
|
||||
},
|
||||
},
|
||||
include: [
|
||||
{
|
||||
model: UserAuthentication,
|
||||
as: "authentications",
|
||||
required: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
// We have an existing invite for his user, so we need to update it with our
|
||||
// new details and link up the authentication method
|
||||
if (invite && !invite.authentications.length) {
|
||||
const transaction = await sequelize.transaction();
|
||||
let auth;
|
||||
|
||||
try {
|
||||
await invite.update(
|
||||
{
|
||||
name,
|
||||
avatarUrl,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
auth = await invite.createAuthentication(authentication, {
|
||||
transaction,
|
||||
});
|
||||
await transaction.commit();
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
|
||||
return {
|
||||
user: invite,
|
||||
authentication: auth,
|
||||
isNewUser: true,
|
||||
};
|
||||
}
|
||||
|
||||
// No auth, no user – this is an entirely new sign in.
|
||||
const transaction = await sequelize.transaction();
|
||||
|
||||
try {
|
||||
const { defaultUserRole } = await Team.findByPk(teamId, {
|
||||
attributes: ["defaultUserRole"],
|
||||
transaction,
|
||||
});
|
||||
const user = await User.create(
|
||||
{
|
||||
name,
|
||||
email,
|
||||
username,
|
||||
isAdmin: typeof isAdmin === "boolean" && isAdmin,
|
||||
isViewer: isAdmin === true ? false : defaultUserRole === "viewer",
|
||||
teamId,
|
||||
avatarUrl,
|
||||
service: null,
|
||||
authentications: [authentication],
|
||||
},
|
||||
{
|
||||
include: "authentications",
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
await Event.create(
|
||||
{
|
||||
name: "users.create",
|
||||
actorId: user.id,
|
||||
userId: user.id,
|
||||
teamId: user.teamId,
|
||||
data: {
|
||||
name: user.name,
|
||||
},
|
||||
ip,
|
||||
},
|
||||
{
|
||||
transaction,
|
||||
}
|
||||
);
|
||||
await transaction.commit();
|
||||
return {
|
||||
user,
|
||||
authentication: user.authentications[0],
|
||||
isNewUser: true,
|
||||
};
|
||||
} catch (err) {
|
||||
await transaction.rollback();
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user