mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
misc: added monitoring for user auth
This commit is contained in:
Vendored
+2
@@ -136,6 +136,8 @@ declare module "@fastify/request-context" {
|
|||||||
interface RequestContextData {
|
interface RequestContextData {
|
||||||
reqId: string;
|
reqId: string;
|
||||||
orgId?: string;
|
orgId?: string;
|
||||||
|
ip?: string;
|
||||||
|
userAgent?: string;
|
||||||
orgName?: string;
|
orgName?: string;
|
||||||
userAuthInfo?: {
|
userAuthInfo?: {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// All the any rules are disabled because passport typesense with fastify is really poor
|
// All the any rules are disabled because passport typesense with fastify is really poor
|
||||||
|
|
||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import { MultiSamlStrategy } from "@node-saml/passport-saml";
|
import { MultiSamlStrategy } from "@node-saml/passport-saml";
|
||||||
import { FastifyRequest } from "fastify";
|
import { FastifyRequest } from "fastify";
|
||||||
@@ -17,6 +18,7 @@ import { ApiDocsTags, SamlSso } from "@app/lib/api-docs";
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { BadRequestError } from "@app/lib/errors";
|
import { BadRequestError } from "@app/lib/errors";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { AuthAttemptAuthMethod, AuthAttemptAuthResult, authAttemptCounter } from "@app/lib/telemetry/metrics";
|
||||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||||
import { SanitizedSamlConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config";
|
import { SanitizedSamlConfigSchema } from "@app/server/routes/sanitizedSchema/directory-config";
|
||||||
@@ -102,15 +104,15 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
|
|
||||||
|
const email =
|
||||||
|
profile?.email ??
|
||||||
|
// entra sends data in this format
|
||||||
|
(profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"] as string) ??
|
||||||
|
(profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved\
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
|
||||||
|
|
||||||
const email =
|
|
||||||
profile?.email ??
|
|
||||||
// entra sends data in this format
|
|
||||||
(profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email"] as string) ??
|
|
||||||
(profile?.emailAddress as string); // emailRippling is added because in Rippling the field `email` reserved\
|
|
||||||
|
|
||||||
const firstName = (profile.firstName ??
|
const firstName = (profile.firstName ??
|
||||||
// entra sends data in this format
|
// entra sends data in this format
|
||||||
profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName"]) as string;
|
profile["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/firstName"]) as string;
|
||||||
@@ -144,7 +146,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
})
|
})
|
||||||
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken, user, organization } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email: email.toLowerCase(),
|
email: email.toLowerCase(),
|
||||||
firstName,
|
firstName,
|
||||||
@@ -154,8 +156,28 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId,
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId,
|
||||||
metadata: userMetadata
|
metadata: userMetadata
|
||||||
});
|
});
|
||||||
|
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email.toLowerCase(),
|
||||||
|
"infisical.user.id": user.id,
|
||||||
|
"infisical.organization.id": organization.id,
|
||||||
|
"infisical.organization.name": organization.name,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.SAML,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email.toLowerCase(),
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.SAML,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(error as Error);
|
cb(error as Error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||||
import { ForbiddenError } from "@casl/ability";
|
import { ForbiddenError } from "@casl/ability";
|
||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
import { Issuer, Issuer as OpenIdIssuer, Strategy as OpenIdStrategy, TokenSet } from "openid-client";
|
||||||
|
|
||||||
import { AccessScope, OrganizationActionScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
import { AccessScope, OrganizationActionScope, OrgMembershipStatus, TableName, TUsers } from "@app/db/schemas";
|
||||||
@@ -15,6 +16,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
|
|||||||
import { getConfig } from "@app/lib/config/env";
|
import { getConfig } from "@app/lib/config/env";
|
||||||
import { crypto } from "@app/lib/crypto";
|
import { crypto } from "@app/lib/crypto";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError, OidcAuthError } from "@app/lib/errors";
|
||||||
|
import { AuthAttemptAuthMethod, AuthAttemptAuthResult, authAttemptCounter } from "@app/lib/telemetry/metrics";
|
||||||
import { OrgServiceActor } from "@app/lib/types";
|
import { OrgServiceActor } from "@app/lib/types";
|
||||||
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
import { ActorType, AuthMethod, AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
@@ -471,7 +473,7 @@ export const oidcConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isUserCompleted, providerAuthToken };
|
return { isUserCompleted, providerAuthToken, user };
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateOidcCfg = async ({
|
const updateOidcCfg = async ({
|
||||||
@@ -754,10 +756,31 @@ export const oidcConfigServiceFactory = ({
|
|||||||
callbackPort,
|
callbackPort,
|
||||||
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
manageGroupMemberships: oidcCfg.manageGroupMemberships
|
||||||
})
|
})
|
||||||
.then(({ isUserCompleted, providerAuthToken }) => {
|
.then(({ isUserCompleted, providerAuthToken, user }) => {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": claims?.email?.toLowerCase(),
|
||||||
|
"infisical.user.id": user.id,
|
||||||
|
"infisical.organization.id": org.id,
|
||||||
|
"infisical.organization.name": org.name,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.OIDC,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": claims?.email?.toLowerCase(),
|
||||||
|
"infisical.organization.id": org.id,
|
||||||
|
"infisical.organization.name": org.name,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.OIDC,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
cb(error);
|
cb(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -769,7 +769,7 @@ export const samlConfigServiceFactory = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return { isUserCompleted, providerAuthToken };
|
return { isUserCompleted, providerAuthToken, user, organization };
|
||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { TSamlConfigs } from "@app/db/schemas";
|
import { TOrganizations, TSamlConfigs, TUsers } from "@app/db/schemas";
|
||||||
import { TOrgPermission } from "@app/lib/types";
|
import { TOrgPermission } from "@app/lib/types";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
|
|
||||||
@@ -78,5 +78,7 @@ export type TSamlConfigServiceFactory = {
|
|||||||
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
samlLogin: (arg: TSamlLoginDTO) => Promise<{
|
||||||
isUserCompleted: boolean;
|
isUserCompleted: boolean;
|
||||||
providerAuthToken: string;
|
providerAuthToken: string;
|
||||||
|
user: TUsers;
|
||||||
|
organization: TOrganizations;
|
||||||
}>;
|
}>;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import opentelemetry from "@opentelemetry/api";
|
||||||
|
|
||||||
|
const infisicalMeter = opentelemetry.metrics.getMeter("Infisical");
|
||||||
|
|
||||||
|
export enum AuthAttemptAuthMethod {
|
||||||
|
EMAIL = "email",
|
||||||
|
SAML = "saml",
|
||||||
|
OIDC = "oidc",
|
||||||
|
GOOGLE = "google",
|
||||||
|
GITHUB = "github",
|
||||||
|
GITLAB = "gitlab"
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum AuthAttemptAuthResult {
|
||||||
|
SUCCESS = "success",
|
||||||
|
FAILURE = "failure"
|
||||||
|
}
|
||||||
|
|
||||||
|
export const authAttemptCounter = infisicalMeter.createCounter("infisical.auth.attempt.count", {
|
||||||
|
description: "Authentication attempts (both successful and failed)",
|
||||||
|
unit: "{attempt}"
|
||||||
|
});
|
||||||
@@ -141,7 +141,9 @@ export const main = async ({
|
|||||||
await server.register(fastifyRequestContext, {
|
await server.register(fastifyRequestContext, {
|
||||||
defaultStoreValues: (req) => ({
|
defaultStoreValues: (req) => ({
|
||||||
reqId: req.id,
|
reqId: req.id,
|
||||||
log: req.log.child({ reqId: req.id })
|
log: req.log.child({ reqId: req.id }),
|
||||||
|
ip: req.realIp,
|
||||||
|
userAgent: req.headers["user-agent"]
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
// All the any rules are disabled because passport typesense with fastify is really poor
|
// All the any rules are disabled because passport typesense with fastify is really poor
|
||||||
|
|
||||||
import { Authenticator } from "@fastify/passport";
|
import { Authenticator } from "@fastify/passport";
|
||||||
|
import { requestContext } from "@fastify/request-context";
|
||||||
import fastifySession from "@fastify/session";
|
import fastifySession from "@fastify/session";
|
||||||
import RedisStore from "connect-redis";
|
import RedisStore from "connect-redis";
|
||||||
import { CronJob } from "cron";
|
import { CronJob } from "cron";
|
||||||
@@ -21,6 +22,7 @@ import { BadRequestError, NotFoundError } from "@app/lib/errors";
|
|||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
import { ms } from "@app/lib/ms";
|
import { ms } from "@app/lib/ms";
|
||||||
import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github";
|
import { fetchGithubEmails, fetchGithubUser } from "@app/lib/requests/github";
|
||||||
|
import { AuthAttemptAuthMethod, AuthAttemptAuthResult, authAttemptCounter } from "@app/lib/telemetry/metrics";
|
||||||
import { authRateLimit } from "@app/server/config/rateLimiter";
|
import { authRateLimit } from "@app/server/config/rateLimiter";
|
||||||
import { addAuthOriginDomainCookie } from "@app/server/lib/cookie";
|
import { addAuthOriginDomainCookie } from "@app/server/lib/cookie";
|
||||||
import { AuthMethod } from "@app/services/auth/auth-type";
|
import { AuthMethod } from "@app/services/auth/auth-type";
|
||||||
@@ -51,30 +53,51 @@ export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req, _accessToken, _refreshToken, profile, cb) => {
|
async (req, _accessToken, _refreshToken, profile, cb) => {
|
||||||
try {
|
// @ts-expect-error this is because this is express type and not fastify
|
||||||
// @ts-expect-error this is because this is express type and not fastify
|
const callbackPort = req.session.get("callbackPort");
|
||||||
const callbackPort = req.session.get("callbackPort");
|
// @ts-expect-error this is because this is express type and not fastify
|
||||||
// @ts-expect-error this is because this is express type and not fastify
|
const orgSlug = req.session.get("orgSlug");
|
||||||
const orgSlug = req.session.get("orgSlug");
|
|
||||||
|
|
||||||
const email = profile?.emails?.[0]?.value;
|
const email = profile?.emails?.[0]?.value;
|
||||||
if (!email)
|
if (!email)
|
||||||
throw new NotFoundError({
|
throw new NotFoundError({
|
||||||
message: "Email not found",
|
message: "Email not found",
|
||||||
name: "OauthGoogleRegister"
|
name: "OauthGoogleRegister"
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { isUserCompleted, providerAuthToken, user, orgId, orgName } =
|
||||||
|
await server.services.login.oauth2Login({
|
||||||
|
email,
|
||||||
|
firstName: profile?.name?.givenName || "",
|
||||||
|
lastName: profile?.name?.familyName || "",
|
||||||
|
authMethod: AuthMethod.GOOGLE,
|
||||||
|
callbackPort,
|
||||||
|
orgSlug
|
||||||
});
|
});
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
authAttemptCounter.add(1, {
|
||||||
email,
|
"infisical.user.email": email,
|
||||||
firstName: profile?.name?.givenName || "",
|
"infisical.user.id": user.id,
|
||||||
lastName: profile?.name?.familyName || "",
|
"infisical.organization.id": orgId,
|
||||||
authMethod: AuthMethod.GOOGLE,
|
"infisical.organization.name": orgName,
|
||||||
callbackPort,
|
"infisical.auth.method": AuthAttemptAuthMethod.GOOGLE,
|
||||||
orgSlug
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
});
|
});
|
||||||
|
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.GOOGLE,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
cb(error as Error, false);
|
cb(error as Error, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -101,27 +124,47 @@ export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
|||||||
},
|
},
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
async (req: any, accessToken: string, _refreshToken: string, _profile: any, done: Function) => {
|
async (req: any, accessToken: string, _refreshToken: string, _profile: any, done: Function) => {
|
||||||
|
const ghEmails = await fetchGithubEmails(accessToken);
|
||||||
|
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
||||||
|
|
||||||
|
if (!email) throw new Error("No primary email found");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ghEmails = await fetchGithubEmails(accessToken);
|
|
||||||
const { email } = ghEmails.filter((gitHubEmail) => gitHubEmail.primary)[0];
|
|
||||||
|
|
||||||
if (!email) throw new Error("No primary email found");
|
|
||||||
|
|
||||||
// profile does not get automatically populated so we need to manually fetch user info
|
// profile does not get automatically populated so we need to manually fetch user info
|
||||||
const user = await fetchGithubUser(accessToken);
|
const githubUser = await fetchGithubUser(accessToken);
|
||||||
|
|
||||||
const callbackPort = req.session.get("callbackPort");
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
const { isUserCompleted, providerAuthToken, user, orgId, orgName } =
|
||||||
email,
|
await server.services.login.oauth2Login({
|
||||||
firstName: user.name || user.login,
|
email,
|
||||||
lastName: "",
|
firstName: githubUser.name || githubUser.login,
|
||||||
authMethod: AuthMethod.GITHUB,
|
lastName: "",
|
||||||
callbackPort
|
authMethod: AuthMethod.GITHUB,
|
||||||
|
callbackPort
|
||||||
|
});
|
||||||
|
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.user.id": user.id,
|
||||||
|
"infisical.organization.id": orgId,
|
||||||
|
"infisical.organization.name": orgName,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.GITHUB,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
});
|
});
|
||||||
|
|
||||||
done(null, { isUserCompleted, providerAuthToken, externalProviderAccessToken: accessToken });
|
done(null, { isUserCompleted, providerAuthToken, externalProviderAccessToken: accessToken });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.GITHUB,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
done(err as Error, false);
|
done(err as Error, false);
|
||||||
}
|
}
|
||||||
@@ -147,20 +190,41 @@ export const registerOauthMiddlewares = (server: FastifyZodProvider) => {
|
|||||||
pkce: true
|
pkce: true
|
||||||
},
|
},
|
||||||
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
async (req: any, _accessToken: string, _refreshToken: string, profile: any, cb: any) => {
|
||||||
|
const email = profile.emails[0].value;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const callbackPort = req.session.get("callbackPort");
|
const callbackPort = req.session.get("callbackPort");
|
||||||
|
|
||||||
const email = profile.emails[0].value;
|
const { isUserCompleted, providerAuthToken, user, orgId, orgName } =
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.login.oauth2Login({
|
await server.services.login.oauth2Login({
|
||||||
email,
|
email,
|
||||||
firstName: profile.displayName || profile.username || "",
|
firstName: profile.displayName || profile.username || "",
|
||||||
lastName: "",
|
lastName: "",
|
||||||
authMethod: AuthMethod.GITLAB,
|
authMethod: AuthMethod.GITLAB,
|
||||||
callbackPort
|
callbackPort
|
||||||
|
});
|
||||||
|
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.user.id": user.id,
|
||||||
|
"infisical.organization.id": orgId,
|
||||||
|
"infisical.organization.name": orgName,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.GITLAB,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
});
|
});
|
||||||
|
|
||||||
return cb(null, { isUserCompleted, providerAuthToken });
|
return cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.GITLAB,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": requestContext.get("ip"),
|
||||||
|
"user_agent.original": requestContext.get("userAgent")
|
||||||
|
});
|
||||||
|
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
cb(error as Error, false);
|
cb(error as Error, false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import { getUserPrivateKey } from "@app/lib/crypto/srp";
|
|||||||
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
import { BadRequestError, DatabaseError, ForbiddenRequestError, UnauthorizedError } from "@app/lib/errors";
|
||||||
import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn";
|
import { getMinExpiresIn, removeTrailingSlash } from "@app/lib/fn";
|
||||||
import { logger } from "@app/lib/logger";
|
import { logger } from "@app/lib/logger";
|
||||||
|
import { AuthAttemptAuthMethod, AuthAttemptAuthResult, authAttemptCounter } from "@app/lib/telemetry/metrics";
|
||||||
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
import { getUserAgentType } from "@app/server/plugins/audit-log";
|
||||||
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
import { getServerCfg } from "@app/services/super-admin/super-admin-service";
|
||||||
|
|
||||||
@@ -385,63 +386,88 @@ export const authLoginServiceFactory = ({
|
|||||||
providerAuthToken?: string;
|
providerAuthToken?: string;
|
||||||
captchaToken?: string;
|
captchaToken?: string;
|
||||||
}) => {
|
}) => {
|
||||||
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
try {
|
||||||
username: email
|
const usersByUsername = await userDAL.findUserEncKeyByUsername({
|
||||||
});
|
username: email
|
||||||
const userEnc =
|
});
|
||||||
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
const userEnc =
|
||||||
|
usersByUsername?.length > 1 ? usersByUsername.find((el) => el.username === email) : usersByUsername?.[0];
|
||||||
|
|
||||||
if (!userEnc) throw new BadRequestError({ message: "User not found" });
|
if (!userEnc) throw new BadRequestError({ message: "User not found" });
|
||||||
|
|
||||||
if (userEnc.encryptionVersion !== UserEncryption.V2) {
|
if (userEnc.encryptionVersion !== UserEncryption.V2) {
|
||||||
throw new BadRequestError({ message: "Legacy encryption scheme not supported", name: "LegacyEncryptionScheme" });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!userEnc.hashedPassword) {
|
|
||||||
if (userEnc.authMethods?.includes(AuthMethod.EMAIL)) {
|
|
||||||
throw new BadRequestError({
|
throw new BadRequestError({
|
||||||
message: "Legacy encryption scheme not supported",
|
message: "Legacy encryption scheme not supported",
|
||||||
name: "LegacyEncryptionScheme"
|
name: "LegacyEncryptionScheme"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new BadRequestError({ message: "No password found" });
|
if (!userEnc.hashedPassword) {
|
||||||
}
|
if (userEnc.authMethods?.includes(AuthMethod.EMAIL)) {
|
||||||
|
throw new BadRequestError({
|
||||||
const { authMethod, organizationId } = getAuthMethodAndOrgId(email, providerAuthToken);
|
message: "Legacy encryption scheme not supported",
|
||||||
await verifyCaptcha(userEnc, captchaToken);
|
name: "LegacyEncryptionScheme"
|
||||||
|
});
|
||||||
if (!(await crypto.hashing().compareHash(password, userEnc.hashedPassword))) {
|
|
||||||
await userDAL.update(
|
|
||||||
{ id: userEnc.userId },
|
|
||||||
{
|
|
||||||
$incr: {
|
|
||||||
consecutiveFailedPasswordAttempts: 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
|
||||||
|
|
||||||
throw new BadRequestError({ message: "Invalid username or email" });
|
throw new BadRequestError({ message: "No password found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { authMethod, organizationId } = getAuthMethodAndOrgId(email, providerAuthToken);
|
||||||
|
await verifyCaptcha(userEnc, captchaToken);
|
||||||
|
|
||||||
|
if (!(await crypto.hashing().compareHash(password, userEnc.hashedPassword))) {
|
||||||
|
await userDAL.update(
|
||||||
|
{ id: userEnc.userId },
|
||||||
|
{
|
||||||
|
$incr: {
|
||||||
|
consecutiveFailedPasswordAttempts: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new BadRequestError({ message: "Invalid username or email" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = await generateUserTokens({
|
||||||
|
user: {
|
||||||
|
...userEnc,
|
||||||
|
id: userEnc.userId
|
||||||
|
},
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
authMethod,
|
||||||
|
organizationId
|
||||||
|
});
|
||||||
|
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.organization.id": organizationId,
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.user.id": userEnc.userId,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.EMAIL,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.SUCCESS,
|
||||||
|
"client.address": ip,
|
||||||
|
"user_agent.original": userAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
tokens: {
|
||||||
|
accessToken: token.access,
|
||||||
|
refreshToken: token.refresh
|
||||||
|
},
|
||||||
|
user: userEnc
|
||||||
|
} as const;
|
||||||
|
} catch (error) {
|
||||||
|
authAttemptCounter.add(1, {
|
||||||
|
"infisical.user.email": email,
|
||||||
|
"infisical.auth.method": AuthAttemptAuthMethod.EMAIL,
|
||||||
|
"infisical.auth.result": AuthAttemptAuthResult.FAILURE,
|
||||||
|
"client.address": ip,
|
||||||
|
"user_agent.original": userAgent
|
||||||
|
});
|
||||||
|
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
const token = await generateUserTokens({
|
|
||||||
user: {
|
|
||||||
...userEnc,
|
|
||||||
id: userEnc.userId
|
|
||||||
},
|
|
||||||
ip,
|
|
||||||
userAgent,
|
|
||||||
authMethod,
|
|
||||||
organizationId
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
tokens: {
|
|
||||||
accessToken: token.access,
|
|
||||||
refreshToken: token.refresh
|
|
||||||
},
|
|
||||||
user: userEnc
|
|
||||||
} as const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const selectOrganization = async ({
|
const selectOrganization = async ({
|
||||||
@@ -965,7 +991,8 @@ export const authLoginServiceFactory = ({
|
|||||||
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
|
expiresIn: appCfg.JWT_PROVIDER_AUTH_LIFETIME
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
return { isUserCompleted, providerAuthToken };
|
|
||||||
|
return { isUserCompleted, providerAuthToken, user, orgId, orgName };
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user