mirror of
https://github.com/n8n-io/n8n.git
synced 2025-03-05 20:50:17 -08:00
* feat(editor): roll out schema view * feat(editor): add posthog tracking * refactor: use composables * refactor: clean up console log * refactor: clean up impl * chore: clean up impl * fix: fix demo var * chore: add comment * refactor: clean up * chore: wrap error func * refactor: clean up import * refactor: make store * feat: enable rudderstack usebeacon, move event to unload * chore: clean up alert * refactor: move tracking from hooks * fix: reload flags on login * fix: add func to setup * fix: clear duplicate import * chore: add console to tesT * chore: add console to tesT * fix: try reload * chore: randomize instnace id for testing * chore: randomize instnace id for testing * chore: add console logs for testing * chore: move random id to fe * chore: use query param for testing * feat: update PostHog api endpoint * feat: update rs host * feat: update rs host * feat: update rs endpoints * refactor: use api host for BE events as well * refactor: refactor out posthog client * feat: add feature flags to login * feat: add feature flags to login * feat: get feature flags to work * feat: add created at to be events * chore: add todos * chore: clean up store * chore: add created at to identify * feat: add posthog config to settings * feat: add bootstrapping * chore: clean up * chore: fix build * fix: get dates to work * fix: get posthog to recognize dates * chore: refactor * fix: update back to number * fix: update key * fix: get experiment evals to work * feat: add posthog to signup router * feat: add feature flags on sign up * chore: clean up * fix: fix import * chore: clean up loading script * feat: add timeout, fix: script loader * fix: test timeout and get working on 8080 * refactor: move out posthog * feat: add experiment tracking * fix: clear tracked on reset * fix: fix signup bug * fix: handle errors when telmetry is disabled * refactor: remove redundant await * fix: add back posthog to telemetry * test: fix test * test: fix test * test: add tests for posthog client * lint: fix * fix: fix issue with slow decide endpoint * lint: fix * lint: fix * lint: fix * lint: fix * chore: address PR feedback * chore: address PR feedback * feat: add onboarding experiment
190 lines
5.4 KiB
TypeScript
190 lines
5.4 KiB
TypeScript
import validator from 'validator';
|
|
import { Get, Post, RestController } from '@/decorators';
|
|
import { AuthError, BadRequestError, InternalServerError } from '@/ResponseHelper';
|
|
import { sanitizeUser, withFeatureFlags } from '@/UserManagement/UserManagementHelper';
|
|
import { issueCookie, resolveJwt } from '@/auth/jwt';
|
|
import { AUTH_COOKIE_NAME } from '@/constants';
|
|
import { Request, Response } from 'express';
|
|
import type { ILogger } from 'n8n-workflow';
|
|
import type { User } from '@db/entities/User';
|
|
import { LoginRequest, UserRequest } from '@/requests';
|
|
import type { Repository } from 'typeorm';
|
|
import { In } from 'typeorm';
|
|
import type { Config } from '@/config';
|
|
import type {
|
|
PublicUser,
|
|
IDatabaseCollections,
|
|
IInternalHooksClass,
|
|
CurrentUser,
|
|
} from '@/Interfaces';
|
|
import { handleEmailLogin, handleLdapLogin } from '@/auth';
|
|
import type { PostHogClient } from '@/posthog';
|
|
|
|
@RestController()
|
|
export class AuthController {
|
|
private readonly config: Config;
|
|
|
|
private readonly logger: ILogger;
|
|
|
|
private readonly internalHooks: IInternalHooksClass;
|
|
|
|
private readonly userRepository: Repository<User>;
|
|
|
|
private readonly postHog?: PostHogClient;
|
|
|
|
constructor({
|
|
config,
|
|
logger,
|
|
internalHooks,
|
|
repositories,
|
|
postHog,
|
|
}: {
|
|
config: Config;
|
|
logger: ILogger;
|
|
internalHooks: IInternalHooksClass;
|
|
repositories: Pick<IDatabaseCollections, 'User'>;
|
|
postHog?: PostHogClient;
|
|
}) {
|
|
this.config = config;
|
|
this.logger = logger;
|
|
this.internalHooks = internalHooks;
|
|
this.userRepository = repositories.User;
|
|
this.postHog = postHog;
|
|
}
|
|
|
|
/**
|
|
* Log in a user.
|
|
* Authless endpoint.
|
|
*/
|
|
@Post('/login')
|
|
async login(req: LoginRequest, res: Response): Promise<PublicUser> {
|
|
const { email, password } = req.body;
|
|
if (!email) throw new Error('Email is required to log in');
|
|
if (!password) throw new Error('Password is required to log in');
|
|
|
|
const user =
|
|
(await handleLdapLogin(email, password)) ?? (await handleEmailLogin(email, password));
|
|
|
|
if (user) {
|
|
await issueCookie(res, user);
|
|
return withFeatureFlags(this.postHog, sanitizeUser(user));
|
|
}
|
|
|
|
throw new AuthError('Wrong username or password. Do you have caps lock on?');
|
|
}
|
|
|
|
/**
|
|
* Manually check the `n8n-auth` cookie.
|
|
*/
|
|
@Get('/login')
|
|
async currentUser(req: Request, res: Response): Promise<CurrentUser> {
|
|
// Manually check the existing cookie.
|
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
const cookieContents = req.cookies?.[AUTH_COOKIE_NAME] as string | undefined;
|
|
|
|
let user: User;
|
|
if (cookieContents) {
|
|
// If logged in, return user
|
|
try {
|
|
user = await resolveJwt(cookieContents);
|
|
return await withFeatureFlags(this.postHog, sanitizeUser(user));
|
|
} catch (error) {
|
|
res.clearCookie(AUTH_COOKIE_NAME);
|
|
}
|
|
}
|
|
|
|
if (this.config.getEnv('userManagement.isInstanceOwnerSetUp')) {
|
|
throw new AuthError('Not logged in');
|
|
}
|
|
|
|
try {
|
|
user = await this.userRepository.findOneOrFail({
|
|
relations: ['globalRole'],
|
|
where: {},
|
|
});
|
|
} catch (error) {
|
|
throw new InternalServerError(
|
|
'No users found in database - did you wipe the users table? Create at least one user.',
|
|
);
|
|
}
|
|
|
|
if (user.email || user.password) {
|
|
throw new InternalServerError('Invalid database state - user has password set.');
|
|
}
|
|
|
|
await issueCookie(res, user);
|
|
return withFeatureFlags(this.postHog, sanitizeUser(user));
|
|
}
|
|
|
|
/**
|
|
* Validate invite token to enable invitee to set up their account.
|
|
* Authless endpoint.
|
|
*/
|
|
@Get('/resolve-signup-token')
|
|
async resolveSignupToken(req: UserRequest.ResolveSignUp) {
|
|
const { inviterId, inviteeId } = req.query;
|
|
|
|
if (!inviterId || !inviteeId) {
|
|
this.logger.debug(
|
|
'Request to resolve signup token failed because of missing user IDs in query string',
|
|
{ inviterId, inviteeId },
|
|
);
|
|
throw new BadRequestError('Invalid payload');
|
|
}
|
|
|
|
// Postgres validates UUID format
|
|
for (const userId of [inviterId, inviteeId]) {
|
|
if (!validator.isUUID(userId)) {
|
|
this.logger.debug('Request to resolve signup token failed because of invalid user ID', {
|
|
userId,
|
|
});
|
|
throw new BadRequestError('Invalid userId');
|
|
}
|
|
}
|
|
|
|
const users = await this.userRepository.find({ where: { id: In([inviterId, inviteeId]) } });
|
|
if (users.length !== 2) {
|
|
this.logger.debug(
|
|
'Request to resolve signup token failed because the ID of the inviter and/or the ID of the invitee were not found in database',
|
|
{ inviterId, inviteeId },
|
|
);
|
|
throw new BadRequestError('Invalid invite URL');
|
|
}
|
|
|
|
const invitee = users.find((user) => user.id === inviteeId);
|
|
if (!invitee || invitee.password) {
|
|
this.logger.error('Invalid invite URL - invitee already setup', {
|
|
inviterId,
|
|
inviteeId,
|
|
});
|
|
throw new BadRequestError('The invitation was likely either deleted or already claimed');
|
|
}
|
|
|
|
const inviter = users.find((user) => user.id === inviterId);
|
|
if (!inviter?.email || !inviter?.firstName) {
|
|
this.logger.error(
|
|
'Request to resolve signup token failed because inviter does not exist or is not set up',
|
|
{
|
|
inviterId: inviter?.id,
|
|
},
|
|
);
|
|
throw new BadRequestError('Invalid request');
|
|
}
|
|
|
|
void this.internalHooks.onUserInviteEmailClick({ inviter, invitee });
|
|
|
|
const { firstName, lastName } = inviter;
|
|
return { inviter: { firstName, lastName } };
|
|
}
|
|
|
|
/**
|
|
* Log out a user.
|
|
* Authless endpoint.
|
|
*/
|
|
@Post('/logout')
|
|
logout(req: Request, res: Response) {
|
|
res.clearCookie(AUTH_COOKIE_NAME);
|
|
return { loggedOut: true };
|
|
}
|
|
}
|