* Add defaultUserRole on server * Handle defaultUserRole on frontend * Handle tests * Handle user role in userCreator * Minor improvments * Fix prettier issue * Undefined when isNewTeam is false * Update app/scenes/Settings/Security.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update app/scenes/Settings/Security.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Update app/scenes/Settings/Security.js Co-authored-by: Tom Moor <tom.moor@gmail.com> * Remove duplicate validation * Update Team.js * fix: Move note out of restricted width wrapper * Move language setting to use 'note' prop * Remove admin option Co-authored-by: Tom Moor <tom.moor@gmail.com>
		
			
				
	
	
		
			177 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			177 lines
		
	
	
		
			4.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
// @flow
 | 
						||
import Sequelize from "sequelize";
 | 
						||
import { Event, Team, User, UserAuthentication } from "../models";
 | 
						||
import { sequelize } from "../sequelize";
 | 
						||
 | 
						||
const Op = Sequelize.Op;
 | 
						||
 | 
						||
type UserCreatorResult = {|
 | 
						||
  user: User,
 | 
						||
  isNewUser: boolean,
 | 
						||
  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) {
 | 
						||
    let 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.
 | 
						||
  let 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;
 | 
						||
  }
 | 
						||
}
 |