mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 15:27:46 +00:00
Finish preliminary refactor/modularization of requireAuth logic
This commit is contained in:
@@ -24,6 +24,9 @@ import { validateRequest } from "../../helpers/validation";
|
|||||||
import * as reqValidator from "../../validation/auth";
|
import * as reqValidator from "../../validation/auth";
|
||||||
|
|
||||||
declare module "jsonwebtoken" {
|
declare module "jsonwebtoken" {
|
||||||
|
export interface AuthnJwtPayload extends jwt.JwtPayload {
|
||||||
|
authTokenType: AuthTokenType;
|
||||||
|
}
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
refreshVersion?: number;
|
refreshVersion?: number;
|
||||||
|
|||||||
+3
-386
@@ -1,396 +1,13 @@
|
|||||||
import { Request } from "express";
|
|
||||||
import { Types } from "mongoose";
|
import { Types } from "mongoose";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import bcrypt from "bcrypt";
|
import { ITokenVersion, TokenVersion } from "../models";
|
||||||
import {
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
APIKeyData,
|
|
||||||
APIKeyDataV2,
|
|
||||||
ITokenVersion,
|
|
||||||
IUser,
|
|
||||||
ServiceTokenData,
|
|
||||||
ServiceTokenDataV3,
|
|
||||||
TokenVersion,
|
|
||||||
User,
|
|
||||||
} from "../models";
|
|
||||||
import {
|
|
||||||
APIKeyDataNotFoundError,
|
|
||||||
AccountNotFoundError,
|
|
||||||
BadRequestError,
|
|
||||||
ServiceTokenDataNotFoundError,
|
|
||||||
UnauthorizedRequestError,
|
|
||||||
} from "../utils/errors";
|
|
||||||
import {
|
import {
|
||||||
getAuthSecret,
|
getAuthSecret,
|
||||||
getJwtAuthLifetime,
|
getJwtAuthLifetime,
|
||||||
getJwtRefreshLifetime
|
getJwtRefreshLifetime
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import {
|
import { AuthTokenType } from "../variables";
|
||||||
AuthMode,
|
|
||||||
AuthTokenType
|
|
||||||
} from "../variables";
|
|
||||||
import {
|
|
||||||
ServiceTokenAuthData,
|
|
||||||
ServiceTokenV3AuthData,
|
|
||||||
UserAuthData
|
|
||||||
} from "../interfaces/middleware";
|
|
||||||
|
|
||||||
import { ActorType } from "../ee/models";
|
|
||||||
import { getUserAgentType } from "../utils/posthog";
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {Object} obj.headers - HTTP request headers object
|
|
||||||
*/
|
|
||||||
export const validateAuthMode = async ({
|
|
||||||
headers,
|
|
||||||
acceptedAuthModes,
|
|
||||||
}: {
|
|
||||||
headers: { [key: string]: string | string[] | undefined },
|
|
||||||
acceptedAuthModes: AuthMode[]
|
|
||||||
}) => {
|
|
||||||
|
|
||||||
const apiKey = headers["x-api-key"];
|
|
||||||
const authHeader = headers["authorization"];
|
|
||||||
|
|
||||||
let authMode, authTokenValue;
|
|
||||||
if (apiKey === undefined && authHeader === undefined) {
|
|
||||||
// case: no auth or X-API-KEY header present
|
|
||||||
throw BadRequestError({ message: "Missing Authorization or X-API-KEY in request header." });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof apiKey === "string") {
|
|
||||||
// case: treat request authentication type as via X-API-KEY (i.e. API Key)
|
|
||||||
authMode = AuthMode.API_KEY;
|
|
||||||
authTokenValue = apiKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof authHeader === "string") {
|
|
||||||
// case: treat request authentication type as via Authorization header (i.e. either JWT or service token)
|
|
||||||
const [tokenType, tokenValue] = <[string, string]>authHeader.split(" ", 2) ?? [null, null]
|
|
||||||
|
|
||||||
if (tokenType === null)
|
|
||||||
throw BadRequestError({ message: "Missing Authorization Header in the request header." });
|
|
||||||
if (tokenType.toLowerCase() !== "bearer")
|
|
||||||
throw BadRequestError({ message: `The provided authentication type '${tokenType}' is not supported.` });
|
|
||||||
if (tokenValue === null)
|
|
||||||
throw BadRequestError({ message: "Missing Authorization Body in the request header." });
|
|
||||||
|
|
||||||
const parts = tokenValue.split(".");
|
|
||||||
|
|
||||||
switch (parts[0]) {
|
|
||||||
case "st":
|
|
||||||
authMode = AuthMode.SERVICE_TOKEN;
|
|
||||||
authTokenValue = tokenValue;
|
|
||||||
break;
|
|
||||||
default: {
|
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
|
||||||
jwt.verify(tokenValue, await getAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType === AuthTokenType.SERVICE_ACCESS_TOKEN) {
|
|
||||||
authMode = AuthMode.SERVICE_ACCESS_TOKEN;
|
|
||||||
} else {
|
|
||||||
authMode = AuthMode.JWT;
|
|
||||||
}
|
|
||||||
|
|
||||||
authTokenValue = tokenValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!authMode || !authTokenValue) throw BadRequestError({ message: "Missing valid Authorization or X-API-KEY in request header." });
|
|
||||||
|
|
||||||
if (!acceptedAuthModes.includes(authMode)) throw BadRequestError({ message: "The provided authentication type is not supported." });
|
|
||||||
|
|
||||||
return ({
|
|
||||||
authMode,
|
|
||||||
authTokenValue,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return user payload corresponding to JWT token [authTokenValue]
|
|
||||||
* that is either for browser / CLI or API Key
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.authTokenValue - JWT token value
|
|
||||||
* @returns {User} user - user corresponding to JWT token
|
|
||||||
*/
|
|
||||||
export const getAuthUserPayload = async ({
|
|
||||||
req,
|
|
||||||
authTokenValue,
|
|
||||||
}: {
|
|
||||||
req: Request,
|
|
||||||
authTokenValue: string;
|
|
||||||
}): Promise<UserAuthData> => {
|
|
||||||
const decodedToken = <jwt.UserIDJwtPayload>(
|
|
||||||
jwt.verify(authTokenValue, await getAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
|
||||||
decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN &&
|
|
||||||
decodedToken.authTokenType !== AuthTokenType.API_KEY
|
|
||||||
) {
|
|
||||||
throw UnauthorizedRequestError();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType === AuthTokenType.ACCESS_TOKEN) {
|
|
||||||
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
|
||||||
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
|
||||||
user: decodedToken.userId
|
|
||||||
}, {
|
|
||||||
lastUsed: new Date(),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!tokenVersion) throw UnauthorizedRequestError();
|
|
||||||
|
|
||||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError();
|
|
||||||
} else if (decodedToken.authTokenType === AuthTokenType.API_KEY) {
|
|
||||||
const apiKeyData = await APIKeyDataV2.findOneAndUpdate(
|
|
||||||
{
|
|
||||||
_id: new Types.ObjectId(decodedToken.apiKeyDataId),
|
|
||||||
user: new Types.ObjectId(decodedToken.userId)
|
|
||||||
},
|
|
||||||
{
|
|
||||||
lastUsed: new Date(),
|
|
||||||
$inc: { usageCount: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!apiKeyData) throw UnauthorizedRequestError();
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.findOne({
|
|
||||||
_id: new Types.ObjectId(decodedToken.userId),
|
|
||||||
}).select("+publicKey +accessVersion");
|
|
||||||
|
|
||||||
if (!user) throw AccountNotFoundError({ message: "Failed to find user" });
|
|
||||||
|
|
||||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor: {
|
|
||||||
type: ActorType.USER,
|
|
||||||
metadata: {
|
|
||||||
userId: user._id.toString(),
|
|
||||||
email: user.email
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authPayload: user,
|
|
||||||
ipAddress: req.realIP,
|
|
||||||
userAgent: req.headers["user-agent"] ?? "",
|
|
||||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return service token data payload corresponding to service token [authTokenValue]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.authTokenValue - service token value
|
|
||||||
* @returns {ServiceTokenData} serviceTokenData - service token data
|
|
||||||
*/
|
|
||||||
export const getAuthSTDPayload = async ({
|
|
||||||
req,
|
|
||||||
authTokenValue,
|
|
||||||
}: {
|
|
||||||
req: Request,
|
|
||||||
authTokenValue: string;
|
|
||||||
}): Promise<ServiceTokenAuthData> => {
|
|
||||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenData
|
|
||||||
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
|
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
|
||||||
throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
|
||||||
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
|
||||||
// case: service token expired
|
|
||||||
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate expired service token",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
|
|
||||||
if (!isMatch) throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate service token",
|
|
||||||
});
|
|
||||||
|
|
||||||
const serviceTokenDataToReturn = await ServiceTokenData
|
|
||||||
.findOneAndUpdate({
|
|
||||||
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
|
|
||||||
}, {
|
|
||||||
lastUsed: new Date(),
|
|
||||||
}, {
|
|
||||||
new: true,
|
|
||||||
})
|
|
||||||
.select("+encryptedKey +iv +tag")
|
|
||||||
|
|
||||||
if (!serviceTokenDataToReturn) throw ServiceTokenDataNotFoundError({ message: "Failed to find service token data" });
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor: {
|
|
||||||
type: ActorType.SERVICE,
|
|
||||||
metadata: {
|
|
||||||
serviceId: serviceTokenDataToReturn._id.toString(),
|
|
||||||
name: serviceTokenDataToReturn.name
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authPayload: serviceTokenDataToReturn,
|
|
||||||
ipAddress: req.realIP,
|
|
||||||
userAgent: req.headers["user-agent"] ?? "",
|
|
||||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return service token data V3 payload corresponding to service token [authTokenValue]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.authTokenValue - service token value
|
|
||||||
* @returns {ServiceTokenData} serviceTokenData - service token data
|
|
||||||
*/
|
|
||||||
export const getAuthSTDV3Payload = async ({
|
|
||||||
req,
|
|
||||||
authTokenValue,
|
|
||||||
}: {
|
|
||||||
req: Request,
|
|
||||||
authTokenValue: string;
|
|
||||||
}): Promise<ServiceTokenV3AuthData> => {
|
|
||||||
|
|
||||||
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
|
||||||
jwt.verify(authTokenValue, await getAuthSecret())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
|
||||||
|
|
||||||
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
|
||||||
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
|
||||||
isActive: true
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!serviceTokenData) {
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate"
|
|
||||||
});
|
|
||||||
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
|
||||||
// case: service token expired
|
|
||||||
await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
isActive: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate",
|
|
||||||
});
|
|
||||||
} else if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
|
||||||
// TODO: raise alarm
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await ServiceTokenDataV3.findByIdAndUpdate(
|
|
||||||
serviceTokenData._id,
|
|
||||||
{
|
|
||||||
accessTokenLastUsed: new Date(),
|
|
||||||
$inc: { accessTokenUsageCount: 1 }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
new: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor: {
|
|
||||||
type: ActorType.SERVICE_V3,
|
|
||||||
metadata: {
|
|
||||||
serviceId: serviceTokenData._id.toString(),
|
|
||||||
name: serviceTokenData.name
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authPayload: serviceTokenData,
|
|
||||||
ipAddress: req.realIP,
|
|
||||||
userAgent: req.headers["user-agent"] ?? "",
|
|
||||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return API key data payload corresponding to API key [authTokenValue]
|
|
||||||
* @param {Object} obj
|
|
||||||
* @param {String} obj.authTokenValue - API key value
|
|
||||||
* @returns {APIKeyData} apiKeyData - API key data
|
|
||||||
*/
|
|
||||||
export const getAuthAPIKeyPayload = async ({
|
|
||||||
req,
|
|
||||||
authTokenValue,
|
|
||||||
}: {
|
|
||||||
req: Request,
|
|
||||||
authTokenValue: string;
|
|
||||||
}): Promise<UserAuthData> => {
|
|
||||||
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
|
||||||
|
|
||||||
let apiKeyData = await APIKeyData
|
|
||||||
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
|
|
||||||
.populate<{ user: IUser }>("user", "+publicKey");
|
|
||||||
|
|
||||||
if (!apiKeyData) {
|
|
||||||
throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
|
|
||||||
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
|
|
||||||
// case: API key expired
|
|
||||||
await APIKeyData.findByIdAndDelete(apiKeyData._id);
|
|
||||||
throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate expired API key",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
|
|
||||||
if (!isMatch) throw UnauthorizedRequestError({
|
|
||||||
message: "Failed to authenticate API key",
|
|
||||||
});
|
|
||||||
|
|
||||||
apiKeyData = await APIKeyData.findOneAndUpdate({
|
|
||||||
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
|
|
||||||
}, {
|
|
||||||
lastUsed: new Date(),
|
|
||||||
}, {
|
|
||||||
new: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!apiKeyData) {
|
|
||||||
throw APIKeyDataNotFoundError({ message: "Failed to find API key data" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await User.findById(apiKeyData.user).select("+publicKey");
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw AccountNotFoundError({
|
|
||||||
message: "Failed to find user",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
actor: {
|
|
||||||
type: ActorType.USER,
|
|
||||||
metadata: {
|
|
||||||
userId: user._id.toString(),
|
|
||||||
email: user.email
|
|
||||||
}
|
|
||||||
},
|
|
||||||
authPayload: user,
|
|
||||||
ipAddress: req.realIP,
|
|
||||||
userAgent: req.headers["user-agent"] ?? "",
|
|
||||||
userAgentType: getUserAgentType(req.headers["user-agent"])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
|
* Return newly issued (JWT) auth and refresh tokens to user with id [userId]
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import {
|
|||||||
import { TelemetryService } from "../services";
|
import { TelemetryService } from "../services";
|
||||||
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
import { client, getEncryptionKey, getRootEncryptionKey } from "../config";
|
||||||
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services";
|
import { EEAuditLogService, EELogService, EESecretService } from "../ee/services";
|
||||||
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/auth/authDataExtractors";
|
import { getAuthDataPayloadIdObj, getAuthDataPayloadUserObj } from "../utils/authn/authDataExtractors";
|
||||||
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
import { getFolderByPath, getFolderIdFromServiceToken } from "../services/FolderService";
|
||||||
import picomatch from "picomatch";
|
import picomatch from "picomatch";
|
||||||
import path from "path";
|
import path from "path";
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import {
|
|
||||||
getAuthAPIKeyPayload,
|
|
||||||
getAuthSTDPayload,
|
|
||||||
getAuthSTDV3Payload,
|
|
||||||
getAuthUserPayload,
|
|
||||||
validateAuthMode,
|
|
||||||
} from "../helpers/auth";
|
|
||||||
import { AuthMode } from "../variables";
|
import { AuthMode } from "../variables";
|
||||||
import { AuthData } from "../interfaces/middleware";
|
import { AuthData } from "../interfaces/middleware";
|
||||||
|
import { extractAuthMode, getAuthData } from "../utils/authn/authMode";
|
||||||
|
import { UnauthorizedRequestError } from "../utils/errors";
|
||||||
|
|
||||||
declare module "jsonwebtoken" {
|
declare module "jsonwebtoken" {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
@@ -33,41 +28,38 @@ const requireAuth = ({
|
|||||||
}) => {
|
}) => {
|
||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
|
||||||
// validate auth token against accepted auth modes [acceptedAuthModes]
|
// extract auth mode
|
||||||
// and return token type [authTokenType] and value [authTokenValue]
|
const { authMode, authTokenValue } = await extractAuthMode({
|
||||||
const { authMode, authTokenValue } = await validateAuthMode({
|
headers: req.headers
|
||||||
headers: req.headers,
|
|
||||||
acceptedAuthModes,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let authData: AuthData;
|
// validate auth mode
|
||||||
|
if (!acceptedAuthModes.includes(authMode)) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate unaccepted authentication mode"
|
||||||
|
});
|
||||||
|
|
||||||
|
// get auth data / payload
|
||||||
|
const authData: AuthData = await getAuthData({
|
||||||
|
authMode,
|
||||||
|
authTokenValue,
|
||||||
|
ipAddress: req.realIP,
|
||||||
|
userAgent: req.headers["user-agent"] ?? ""
|
||||||
|
});
|
||||||
|
|
||||||
switch (authMode) {
|
switch (authMode) {
|
||||||
case AuthMode.SERVICE_TOKEN:
|
case AuthMode.SERVICE_TOKEN:
|
||||||
authData = await getAuthSTDPayload({
|
|
||||||
req,
|
|
||||||
authTokenValue,
|
|
||||||
});
|
|
||||||
req.serviceTokenData = authData.authPayload;
|
req.serviceTokenData = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
case AuthMode.SERVICE_ACCESS_TOKEN:
|
case AuthMode.SERVICE_ACCESS_TOKEN:
|
||||||
authData = await getAuthSTDV3Payload({
|
req.serviceTokenData = authData.authPayload;
|
||||||
req,
|
|
||||||
authTokenValue
|
|
||||||
});
|
|
||||||
break;
|
break;
|
||||||
case AuthMode.API_KEY:
|
case AuthMode.API_KEY:
|
||||||
authData = await getAuthAPIKeyPayload({
|
req.user = authData.authPayload;
|
||||||
req,
|
break;
|
||||||
authTokenValue
|
case AuthMode.API_KEY_V2:
|
||||||
});
|
|
||||||
req.user = authData.authPayload;
|
req.user = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
case AuthMode.JWT:
|
case AuthMode.JWT:
|
||||||
authData = await getAuthUserPayload({
|
|
||||||
req,
|
|
||||||
authTokenValue
|
|
||||||
});
|
|
||||||
req.user = authData.authPayload;
|
req.user = authData.authPayload;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { AuthMode } from "../../variables";
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw",
|
"/raw",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
secretsController.getSecretsRaw
|
secretsController.getSecretsRaw
|
||||||
);
|
);
|
||||||
@@ -15,7 +15,7 @@ router.get(
|
|||||||
router.get(
|
router.get(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -29,7 +29,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -43,7 +43,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -57,7 +57,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/raw/:secretName",
|
"/raw/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -71,7 +71,7 @@ router.delete(
|
|||||||
router.get(
|
router.get(
|
||||||
"/",
|
"/",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -83,7 +83,7 @@ router.get(
|
|||||||
router.post(
|
router.post(
|
||||||
"/batch",
|
"/batch",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -94,7 +94,7 @@ router.post(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/batch",
|
"/batch",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -105,7 +105,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/batch",
|
"/batch",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -116,7 +116,7 @@ router.delete(
|
|||||||
router.post(
|
router.post(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -127,7 +127,7 @@ router.post(
|
|||||||
router.get(
|
router.get(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "query"
|
locationWorkspaceId: "query"
|
||||||
@@ -138,7 +138,7 @@ router.get(
|
|||||||
router.patch(
|
router.patch(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
@@ -149,7 +149,7 @@ router.patch(
|
|||||||
router.delete(
|
router.delete(
|
||||||
"/:secretName",
|
"/:secretName",
|
||||||
requireAuth({
|
requireAuth({
|
||||||
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
acceptedAuthModes: [AuthMode.JWT, AuthMode.API_KEY, AuthMode.API_KEY_V2, AuthMode.SERVICE_TOKEN, AuthMode.SERVICE_ACCESS_TOKEN]
|
||||||
}),
|
}),
|
||||||
requireBlindIndicesEnabled({
|
requireBlindIndicesEnabled({
|
||||||
locationWorkspaceId: "body"
|
locationWorkspaceId: "body"
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import {
|
||||||
|
APIKeyData,
|
||||||
|
IUser,
|
||||||
|
User
|
||||||
|
} from "../../../models";
|
||||||
|
import { AccountNotFoundError, UnauthorizedRequestError } from "../../errors";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
|
interface ValidateAPIKeyParams {
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateAPIKey = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: ValidateAPIKeyParams) => {
|
||||||
|
|
||||||
|
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||||
|
|
||||||
|
let apiKeyData = await APIKeyData
|
||||||
|
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
|
||||||
|
.populate<{ user: IUser }>("user", "+publicKey");
|
||||||
|
|
||||||
|
if (!apiKeyData) {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
} else if (apiKeyData?.expiresAt && new Date(apiKeyData.expiresAt) < new Date()) {
|
||||||
|
// case: API key expired
|
||||||
|
await APIKeyData.findByIdAndDelete(apiKeyData._id);
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatch = await bcrypt.compare(TOKEN_SECRET, apiKeyData.secretHash);
|
||||||
|
if (!isMatch) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
apiKeyData = await APIKeyData.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date(),
|
||||||
|
}, {
|
||||||
|
new: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKeyData) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const user = await User.findById(apiKeyData.user).select("+publicKey");
|
||||||
|
|
||||||
|
if (!user) throw AccountNotFoundError();
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { APIKeyDataV2, User } from "../../../models";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
import { AccountNotFoundError, UnauthorizedRequestError } from "../../errors";
|
||||||
|
|
||||||
|
interface ValidateAPIKeyV2Params {
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateAPIKeyV2 = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: ValidateAPIKeyV2Params) => {
|
||||||
|
|
||||||
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.API_KEY) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const apiKeyData = await APIKeyDataV2.findByIdAndUpdate(
|
||||||
|
decodedToken.apiKeyDataId,
|
||||||
|
{
|
||||||
|
lastUsed: new Date(),
|
||||||
|
$inc: { usageCount: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!apiKeyData) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const user = await User.findById(apiKeyData.user).select("+publicKey");
|
||||||
|
|
||||||
|
if (!user) throw AccountNotFoundError();
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,191 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { ActorType } from "../../../ee/models";
|
||||||
|
import { AuthMode, AuthTokenType } from "../../../variables";
|
||||||
|
import { UnauthorizedRequestError } from "../../errors";
|
||||||
|
import { validateAPIKey } from "./apiKey";
|
||||||
|
import { validateAPIKeyV2 } from "./apiKeyV2";
|
||||||
|
import { validateServiceTokenV2 } from "./serviceTokenV2";
|
||||||
|
import { validateServiceTokenV3 } from "./serviceTokenV3";
|
||||||
|
import { validateJWT } from "./jwt";
|
||||||
|
import { getUserAgentType } from "../../posthog";
|
||||||
|
import { AuthData } from "../../../interfaces/middleware";
|
||||||
|
|
||||||
|
interface ExtractAuthModeParams {
|
||||||
|
headers: { [key: string]: string | string[] | undefined }
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ExtractAuthModeReturn {
|
||||||
|
authMode: AuthMode;
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetAuthDataParams {
|
||||||
|
authMode: AuthMode;
|
||||||
|
authTokenValue: string;
|
||||||
|
ipAddress: string;
|
||||||
|
userAgent: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the recognized authentication mode based on token in [headers]; accepted token types include:
|
||||||
|
* - SERVICE_TOKEN
|
||||||
|
* - API_KEY
|
||||||
|
* - JWT
|
||||||
|
* - SERVICE_ACCESS_TOKEN (from ST V3)
|
||||||
|
* - API_KEY_V2
|
||||||
|
* @param {Object} params
|
||||||
|
* @param {Object.<string, (string|string[]|undefined)>} params.headers - The HTTP request headers, usually from Express's `req.headers`.
|
||||||
|
* @returns {Promise<AuthMode>} The derived authentication mode based on the headers.
|
||||||
|
* @throws {UnauthorizedError} Throws an error if no applicable authMode is found.
|
||||||
|
*/
|
||||||
|
export const extractAuthMode = async ({
|
||||||
|
headers
|
||||||
|
}: ExtractAuthModeParams): Promise<ExtractAuthModeReturn> => {
|
||||||
|
|
||||||
|
const apiKey = headers["x-api-key"] as string;
|
||||||
|
const authHeader = headers["authorization"] as string;
|
||||||
|
|
||||||
|
if (apiKey) {
|
||||||
|
return { authMode: AuthMode.API_KEY, authTokenValue: apiKey };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!authHeader) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate unknown authentication method"
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authHeader.startsWith("Bearer ")) throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate unknown authentication method"
|
||||||
|
});
|
||||||
|
|
||||||
|
const authTokenValue = authHeader.slice(7);
|
||||||
|
|
||||||
|
if (authTokenValue.startsWith("st.")) {
|
||||||
|
return { authMode: AuthMode.SERVICE_TOKEN, authTokenValue };
|
||||||
|
}
|
||||||
|
|
||||||
|
const decodedToken = <jwt.AuthnJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
switch (decodedToken.authTokenType) {
|
||||||
|
case AuthTokenType.ACCESS_TOKEN:
|
||||||
|
return { authMode: AuthMode.JWT, authTokenValue };
|
||||||
|
case AuthTokenType.API_KEY:
|
||||||
|
return { authMode: AuthMode.API_KEY_V2, authTokenValue };
|
||||||
|
case AuthTokenType.SERVICE_ACCESS_TOKEN:
|
||||||
|
return { authMode: AuthMode.SERVICE_ACCESS_TOKEN, authTokenValue };
|
||||||
|
default:
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate unknown authentication method"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getAuthData = async ({
|
||||||
|
authMode,
|
||||||
|
authTokenValue,
|
||||||
|
ipAddress,
|
||||||
|
userAgent
|
||||||
|
}: GetAuthDataParams): Promise<AuthData> => {
|
||||||
|
|
||||||
|
const userAgentType = getUserAgentType(userAgent);
|
||||||
|
|
||||||
|
switch (authMode) {
|
||||||
|
case AuthMode.SERVICE_TOKEN: {
|
||||||
|
const serviceTokenData = await validateServiceTokenV2({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor: {
|
||||||
|
type: ActorType.SERVICE,
|
||||||
|
metadata: {
|
||||||
|
serviceId: serviceTokenData._id.toString(),
|
||||||
|
name: serviceTokenData.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authPayload: serviceTokenData,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
userAgentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AuthMode.SERVICE_ACCESS_TOKEN: {
|
||||||
|
const serviceTokenData = await validateServiceTokenV3({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor: {
|
||||||
|
type: ActorType.SERVICE_V3,
|
||||||
|
metadata: {
|
||||||
|
serviceId: serviceTokenData._id.toString(),
|
||||||
|
name: serviceTokenData.name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authPayload: serviceTokenData,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
userAgentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AuthMode.API_KEY: {
|
||||||
|
const user = await validateAPIKey({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor: {
|
||||||
|
type: ActorType.USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user._id.toString(),
|
||||||
|
email: user.email
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authPayload: user,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
userAgentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AuthMode.API_KEY_V2: {
|
||||||
|
const user = await validateAPIKeyV2({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor: {
|
||||||
|
type: ActorType.USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user._id.toString(),
|
||||||
|
email: user.email
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authPayload: user,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
userAgentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case AuthMode.JWT: {
|
||||||
|
const user = await validateJWT({
|
||||||
|
authTokenValue
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
actor: {
|
||||||
|
type: ActorType.USER,
|
||||||
|
metadata: {
|
||||||
|
userId: user._id.toString(),
|
||||||
|
email: user.email
|
||||||
|
}
|
||||||
|
},
|
||||||
|
authPayload: user,
|
||||||
|
ipAddress,
|
||||||
|
userAgent,
|
||||||
|
userAgentType
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export {
|
||||||
|
extractAuthMode,
|
||||||
|
getAuthData
|
||||||
|
} from "./helpers";
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { TokenVersion, User } from "../../../models";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
import { AccountNotFoundError, UnauthorizedRequestError } from "../../errors";
|
||||||
|
|
||||||
|
interface ValidateJWTParams {
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateJWT = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: ValidateJWTParams) => {
|
||||||
|
|
||||||
|
const decodedToken = <jwt.UserIDJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.ACCESS_TOKEN) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
||||||
|
user: decodedToken.userId
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!tokenVersion) throw UnauthorizedRequestError();
|
||||||
|
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const user = await User.findOne({
|
||||||
|
_id: new Types.ObjectId(decodedToken.userId),
|
||||||
|
}).select("+publicKey");
|
||||||
|
|
||||||
|
if (!user) throw AccountNotFoundError({ message: "Failed to find user" });
|
||||||
|
|
||||||
|
if (!user?.publicKey) throw UnauthorizedRequestError({ message: "Failed to authenticate user with partially set up account" });
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
import { ServiceTokenData } from "../../../models";
|
||||||
|
import { ResourceNotFoundError, UnauthorizedRequestError } from "../../errors";
|
||||||
|
import bcrypt from "bcrypt";
|
||||||
|
|
||||||
|
interface ValidateServiceTokenV2Params {
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateServiceTokenV2 = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: ValidateServiceTokenV2Params) => {
|
||||||
|
const [_, TOKEN_IDENTIFIER, TOKEN_SECRET] = <[string, string, string]>authTokenValue.split(".", 3);
|
||||||
|
|
||||||
|
const serviceTokenData = await ServiceTokenData
|
||||||
|
.findById(TOKEN_IDENTIFIER, "+secretHash +expiresAt")
|
||||||
|
|
||||||
|
if (!serviceTokenData) {
|
||||||
|
throw UnauthorizedRequestError();
|
||||||
|
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
||||||
|
// case: service token expired
|
||||||
|
await ServiceTokenData.findByIdAndDelete(serviceTokenData._id);
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate expired service token",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const isMatch = await bcrypt.compare(TOKEN_SECRET, serviceTokenData.secretHash);
|
||||||
|
if (!isMatch) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const serviceTokenDataToReturn = await ServiceTokenData
|
||||||
|
.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(TOKEN_IDENTIFIER),
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date(),
|
||||||
|
}, {
|
||||||
|
new: true,
|
||||||
|
})
|
||||||
|
.select("+encryptedKey +iv +tag")
|
||||||
|
|
||||||
|
if (!serviceTokenDataToReturn) throw ResourceNotFoundError();
|
||||||
|
|
||||||
|
return serviceTokenDataToReturn;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import jwt from "jsonwebtoken";
|
||||||
|
import { Types } from "mongoose";
|
||||||
|
import { ServiceTokenDataV3 } from "../../../models";
|
||||||
|
import { getAuthSecret } from "../../../config";
|
||||||
|
import { AuthTokenType } from "../../../variables";
|
||||||
|
import { UnauthorizedRequestError } from "../../errors";
|
||||||
|
|
||||||
|
interface ValidateServiceTokenV3Params {
|
||||||
|
authTokenValue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const validateServiceTokenV3 = async ({
|
||||||
|
authTokenValue
|
||||||
|
}: ValidateServiceTokenV3Params) => {
|
||||||
|
const decodedToken = <jwt.ServiceRefreshTokenJwtPayload>(
|
||||||
|
jwt.verify(authTokenValue, await getAuthSecret())
|
||||||
|
);
|
||||||
|
|
||||||
|
if (decodedToken.authTokenType !== AuthTokenType.SERVICE_ACCESS_TOKEN) throw UnauthorizedRequestError();
|
||||||
|
|
||||||
|
const serviceTokenData = await ServiceTokenDataV3.findOne({
|
||||||
|
_id: new Types.ObjectId(decodedToken.serviceTokenDataId),
|
||||||
|
isActive: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!serviceTokenData) {
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate"
|
||||||
|
});
|
||||||
|
} else if (serviceTokenData?.expiresAt && new Date(serviceTokenData.expiresAt) < new Date()) {
|
||||||
|
// case: service token expired
|
||||||
|
await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
isActive: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate",
|
||||||
|
});
|
||||||
|
} else if (decodedToken.tokenVersion !== serviceTokenData.tokenVersion) {
|
||||||
|
// TODO: raise alarm
|
||||||
|
throw UnauthorizedRequestError({
|
||||||
|
message: "Failed to authenticate",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await ServiceTokenDataV3.findByIdAndUpdate(
|
||||||
|
serviceTokenData._id,
|
||||||
|
{
|
||||||
|
accessTokenLastUsed: new Date(),
|
||||||
|
$inc: { accessTokenUsageCount: 1 }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
new: true
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return serviceTokenData;
|
||||||
|
}
|
||||||
@@ -31,7 +31,7 @@ import {
|
|||||||
initializeGitLabStrategy,
|
initializeGitLabStrategy,
|
||||||
initializeGoogleStrategy,
|
initializeGoogleStrategy,
|
||||||
initializeSamlStrategy
|
initializeSamlStrategy
|
||||||
} from "../auth/passport";
|
} from "../authn/passport";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare Infisical upon startup. This includes tasks like:
|
* Prepare Infisical upon startup. This includes tasks like:
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ export enum AuthMode {
|
|||||||
JWT = "jwt",
|
JWT = "jwt",
|
||||||
SERVICE_TOKEN = "serviceToken",
|
SERVICE_TOKEN = "serviceToken",
|
||||||
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
SERVICE_ACCESS_TOKEN = "serviceAccessToken",
|
||||||
API_KEY = "apiKey"
|
API_KEY = "apiKey",
|
||||||
|
API_KEY_V2 = "apiKeyV2"
|
||||||
}
|
}
|
||||||
|
|
||||||
export const K8_USER_AGENT_NAME = "k8-operator"
|
export const K8_USER_AGENT_NAME = "k8-operator"
|
||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
// import { APIKeyV2Section } from "../APIKeyV2Section";
|
|
||||||
import { APIKeySection } from "../APIKeySection";
|
import { APIKeySection } from "../APIKeySection";
|
||||||
|
import { APIKeyV2Section } from "../APIKeyV2Section";
|
||||||
|
|
||||||
export const PersonalAPIKeyTab = () => {
|
export const PersonalAPIKeyTab = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <APIKeyV2Section /> */}
|
<APIKeyV2Section />
|
||||||
<APIKeySection />
|
<APIKeySection />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-2
@@ -1,10 +1,10 @@
|
|||||||
import { ServiceTokenSection } from "../ServiceTokenSection";
|
import { ServiceTokenSection } from "../ServiceTokenSection";
|
||||||
// import { ServiceTokenV3Section } from "../ServiceTokenV3Section";
|
import { ServiceTokenV3Section } from "../ServiceTokenV3Section";
|
||||||
|
|
||||||
export const ProjectServiceTokensTab = () => {
|
export const ProjectServiceTokensTab = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{/* <ServiceTokenV3Section /> */}
|
<ServiceTokenV3Section />
|
||||||
<ServiceTokenSection />
|
<ServiceTokenSection />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user