diff --git a/backend/src/controllers/v1/authController.ts b/backend/src/controllers/v1/authController.ts index 19850680eb..6e2de53b31 100644 --- a/backend/src/controllers/v1/authController.ts +++ b/backend/src/controllers/v1/authController.ts @@ -4,16 +4,21 @@ import jwt from 'jsonwebtoken'; import * as Sentry from '@sentry/node'; import * as bigintConversion from 'bigint-conversion'; const jsrp = require('jsrp'); -import { User } from '../../models'; +import { User, LoginSRPDetail } from '../../models'; import { createToken, issueTokens, clearTokens } from '../../helpers/auth'; +import { + ACTION_LOGIN, + ACTION_LOGOUT +} from '../../variables'; import { NODE_ENV, JWT_AUTH_LIFETIME, JWT_AUTH_SECRET, JWT_REFRESH_SECRET } from '../../config'; -import LoginSRPDetail from '../../models/LoginSRPDetail'; import { BadRequestError } from '../../utils/errors'; +import { EELogService } from '../../ee/services'; +import { getChannelFromUserAgent } from '../../utils/posthog'; // TODO: move this declare module 'jsonwebtoken' { export interface UserIDJwtPayload extends jwt.JwtPayload { @@ -116,6 +121,18 @@ export const login2 = async (req: Request, res: Response) => { secure: NODE_ENV === 'production' ? true : false }); + const loginAction = await EELogService.createAction({ + name: ACTION_LOGIN, + userId: user._id + }); + + loginAction && await EELogService.createLog({ + userId: user._id, + actions: [loginAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + // return (access) token in response return res.status(200).send({ token: tokens.token, @@ -159,6 +176,19 @@ export const logout = async (req: Request, res: Response) => { sameSite: 'strict', secure: NODE_ENV === 'production' ? true : false }); + + const logoutAction = await EELogService.createAction({ + name: ACTION_LOGOUT, + userId: req.user._id + }); + + logoutAction && await EELogService.createLog({ + userId: req.user._id, + actions: [logoutAction], + channel: getChannelFromUserAgent(req.headers['user-agent']), + ipAddress: req.ip + }); + } catch (err) { Sentry.setUser({ email: req.user.email }); Sentry.captureException(err); diff --git a/backend/src/controllers/v1/membershipOrgController.ts b/backend/src/controllers/v1/membershipOrgController.ts index f3703b8895..0040b583e6 100644 --- a/backend/src/controllers/v1/membershipOrgController.ts +++ b/backend/src/controllers/v1/membershipOrgController.ts @@ -77,8 +77,6 @@ export const changeMembershipOrgRole = async (req: Request, res: Response) => { // change role for (target) organization membership with id // [membershipOrgId] - // TODO - let membershipToChangeRole; // try { // } catch (err) { diff --git a/backend/src/controllers/v1/passwordController.ts b/backend/src/controllers/v1/passwordController.ts index d429cbf41f..0c5530f58d 100644 --- a/backend/src/controllers/v1/passwordController.ts +++ b/backend/src/controllers/v1/passwordController.ts @@ -4,12 +4,11 @@ import crypto from 'crypto'; // eslint-disable-next-line @typescript-eslint/no-var-requires const jsrp = require('jsrp'); import * as bigintConversion from 'bigint-conversion'; -import { User, Token, BackupPrivateKey } from '../../models'; +import { User, Token, BackupPrivateKey, LoginSRPDetail } from '../../models'; import { checkEmailVerification } from '../../helpers/signup'; import { createToken } from '../../helpers/auth'; import { sendMail } from '../../helpers/nodemailer'; import { JWT_SIGNUP_LIFETIME, JWT_SIGNUP_SECRET, SITE_URL } from '../../config'; -import LoginSRPDetail from '../../models/LoginSRPDetail'; import { BadRequestError } from '../../utils/errors'; /** diff --git a/backend/src/controllers/v2/secretsController.ts b/backend/src/controllers/v2/secretsController.ts index 90eb8e48d2..5c6f5898e9 100644 --- a/backend/src/controllers/v2/secretsController.ts +++ b/backend/src/controllers/v2/secretsController.ts @@ -79,7 +79,7 @@ export const createSecrets = async (req: Request, res: Response) => { */ const channel = getChannelFromUserAgent(req.headers['user-agent']) - const { workspaceId, environment } = req.body; + const { workspaceId, environment }: { workspaceId: string, environment: string } = req.body; const hasAccess = await userHasWorkspaceAccess(req.user, workspaceId, environment, ABILITY_WRITE) if (!hasAccess) { @@ -175,17 +175,17 @@ export const createSecrets = async (req: Request, res: Response) => { })) }); - const addAction = await EELogService.createActionSecret({ + const addAction = await EELogService.createAction({ name: ACTION_ADD_SECRETS, - userId: req.user._id.toString(), - workspaceId, + userId: req.user._id, + workspaceId: new Types.ObjectId(workspaceId), secretIds: newSecrets.map((n) => n._id) }); // (EE) create (audit) log addAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId, + workspaceId: new Types.ObjectId(workspaceId), actions: [addAction], channel, ipAddress: req.ip @@ -300,16 +300,16 @@ export const getSecrets = async (req: Request, res: Response) => { const channel = getChannelFromUserAgent(req.headers['user-agent']) - const readAction = await EELogService.createActionSecret({ + const readAction = await EELogService.createAction({ name: ACTION_READ_SECRETS, - userId: userId, - workspaceId: workspaceId as string, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId as string), secretIds: secrets.map((n: any) => n._id) }); readAction && await EELogService.createLog({ - userId: userId, - workspaceId: workspaceId as string, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId as string), actions: [readAction], channel, ipAddress: req.ip @@ -505,17 +505,17 @@ export const updateSecrets = async (req: Request, res: Response) => { }); }, 10000); - const updateAction = await EELogService.createActionSecret({ + const updateAction = await EELogService.createAction({ name: ACTION_UPDATE_SECRETS, - userId: req.user._id.toString(), - workspaceId: key, + userId: req.user._id, + workspaceId: new Types.ObjectId(key), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id) }); // (EE) create (audit) log updateAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId: key, + workspaceId: new Types.ObjectId(key), actions: [updateAction], channel, ipAddress: req.ip @@ -631,17 +631,17 @@ export const deleteSecrets = async (req: Request, res: Response) => { workspaceId: key }) }); - const deleteAction = await EELogService.createActionSecret({ + const deleteAction = await EELogService.createAction({ name: ACTION_DELETE_SECRETS, - userId: req.user._id.toString(), - workspaceId: key, + userId: req.user._id, + workspaceId: new Types.ObjectId(key), secretIds: workspaceSecretObj[key].map((secret: ISecret) => secret._id) }); // (EE) create (audit) log deleteAction && await EELogService.createLog({ userId: req.user._id.toString(), - workspaceId: key, + workspaceId: new Types.ObjectId(key), actions: [deleteAction], channel, ipAddress: req.ip diff --git a/backend/src/ee/helpers/action.ts b/backend/src/ee/helpers/action.ts index 389cfedbf1..eb94810f48 100644 --- a/backend/src/ee/helpers/action.ts +++ b/backend/src/ee/helpers/action.ts @@ -1,39 +1,40 @@ import * as Sentry from '@sentry/node'; import { Types } from 'mongoose'; -import { SecretVersion, Action } from '../models'; +import { Action } from '../models'; import { getLatestSecretVersionIds, getLatestNSecretSecretVersionIds } from '../helpers/secretVersion'; -import { ACTION_UPDATE_SECRETS } from '../../variables'; +import { + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS, + ACTION_UPDATE_SECRETS, +} from '../../variables'; /** - * Create an (audit) action for secrets including - * add, delete, update, and read actions. + * Create an (audit) action for updating secrets * @param {Object} obj * @param {String} obj.name - name of action - * @param {ObjectId[]} obj.secretIds - ids of relevant secrets + * @param {Types.ObjectId} obj.secretIds - ids of relevant secrets * @returns {Action} action - new action */ -const createActionSecretHelper = async ({ +const createActionUpdateSecret = async ({ name, userId, workspaceId, secretIds }: { name: string; - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId: Types.ObjectId; secretIds: Types.ObjectId[]; }) => { - let action; - let latestSecretVersions; try { - if (name === ACTION_UPDATE_SECRETS) { - // case: action is updating secrets - // -> add old and new secret versions - latestSecretVersions = (await getLatestNSecretSecretVersionIds({ + const latestSecretVersions = (await getLatestNSecretSecretVersionIds({ secretIds, n: 2 })) @@ -41,17 +42,7 @@ const createActionSecretHelper = async ({ oldSecretVersion: s.versions[0]._id, newSecretVersion: s.versions[1]._id })); - } else { - // case: action is adding, deleting, or reading secrets - // -> add new secret versions - latestSecretVersions = (await getLatestSecretVersionIds({ - secretIds - })) - .map((s) => ({ - newSecretVersion: s.versionId - })); - } - + action = await new Action({ name, user: userId, @@ -64,10 +55,148 @@ const createActionSecretHelper = async ({ } catch (err) { Sentry.setUser(null); Sentry.captureException(err); + throw new Error('Failed to create update secret action'); + } + + return action; +} + +/** + * Create an (audit) action for creating, reading, and deleting + * secrets + * @param {Object} obj + * @param {String} obj.name - name of action + * @param {Types.ObjectId} obj.secretIds - ids of relevant secrets + * @returns {Action} action - new action + */ +const createActionSecret = async ({ + name, + userId, + workspaceId, + secretIds +}: { + name: string; + userId: Types.ObjectId; + workspaceId: Types.ObjectId; + secretIds: Types.ObjectId[]; +}) => { + let action; + try { + // case: action is adding, deleting, or reading secrets + // -> add new secret versions + const latestSecretVersions = (await getLatestSecretVersionIds({ + secretIds + })) + .map((s) => ({ + newSecretVersion: s.versionId + })); + + action = await new Action({ + name, + user: userId, + workspace: workspaceId, + payload: { + secretVersions: latestSecretVersions + } + }).save(); + + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to create action create/read/delete secret action'); + } + + return action; +} + +/** + * Create an (audit) action for user with id [userId] + * @param {Object} obj + * @param {String} obj.name - name of action + * @param {String} obj.userId - id of user associated with action + * @returns + */ +const createActionUser = ({ + name, + userId +}: { + name: string; + userId: Types.ObjectId; +}) => { + let action; + try { + action = new Action({ + name, + user: userId + }).save(); + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); + throw new Error('Failed to create user action'); + } + + return action; +} + +/** + * Create an (audit) action. + * @param {Object} obj + * @param {Object} obj.name - name of action + * @param {Types.ObjectId} obj.userId - id of user associated with action + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with action + * @param {Types.ObjectId[]} obj.secretIds - ids of secrets associated with action +*/ +const createActionHelper = async ({ + name, + userId, + workspaceId, + secretIds, +}: { + name: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; + secretIds?: Types.ObjectId[]; +}) => { + let action; + try { + switch (name) { + case ACTION_LOGIN: + case ACTION_LOGOUT: + action = await createActionUser({ + name, + userId + }); + break; + case ACTION_ADD_SECRETS: + case ACTION_READ_SECRETS: + case ACTION_DELETE_SECRETS: + if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); + action = await createActionSecret({ + name, + userId, + workspaceId, + secretIds + }); + break; + case ACTION_UPDATE_SECRETS: + if (!workspaceId || !secretIds) throw new Error('Missing required params workspace id or secret ids to create action secret'); + action = await createActionUpdateSecret({ + name, + userId, + workspaceId, + secretIds + }); + break; + } + } catch (err) { + Sentry.setUser(null); + Sentry.captureException(err); throw new Error('Failed to create action'); } return action; } -export { createActionSecretHelper }; \ No newline at end of file +export { + createActionHelper +}; \ No newline at end of file diff --git a/backend/src/ee/helpers/log.ts b/backend/src/ee/helpers/log.ts index c357c98184..bdb0a2380a 100644 --- a/backend/src/ee/helpers/log.ts +++ b/backend/src/ee/helpers/log.ts @@ -1,9 +1,19 @@ import * as Sentry from '@sentry/node'; +import { Types } from 'mongoose'; import { Log, IAction } from '../models'; - +/** + * Create an (audit) log + * @param {Object} obj + * @param {Types.ObjectId} obj.userId - id of user associated with the log + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the log + * @param {IAction[]} obj.actions - actions to include in log + * @param {String} obj.channel - channel (web/cli/auto) associated with the log + * @param {String} obj.ipAddress - ip address associated with the log + * @returns {Log} log - new audit log + */ const createLogHelper = async ({ userId, workspaceId, @@ -11,8 +21,8 @@ const createLogHelper = async ({ channel, ipAddress }: { - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; actions: IAction[]; channel: string; ipAddress: string; @@ -21,7 +31,7 @@ const createLogHelper = async ({ try { log = await new Log({ user: userId, - workspace: workspaceId, + workspace: workspaceId ?? undefined, actionNames: actions.map((a) => a.name), actions, channel, diff --git a/backend/src/ee/models/action.ts b/backend/src/ee/models/action.ts index 3d48aa04d5..7cdd7a3552 100644 --- a/backend/src/ee/models/action.ts +++ b/backend/src/ee/models/action.ts @@ -1,10 +1,18 @@ import { Schema, model, Types } from 'mongoose'; +import { + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_UPDATE_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS +} from '../../variables'; export interface IAction { name: string; user?: Types.ObjectId, workspace?: Types.ObjectId, - payload: { + payload?: { secretVersions?: Types.ObjectId[] } } @@ -13,7 +21,15 @@ const actionSchema = new Schema( { name: { type: String, - required: true + required: true, + enum: [ + ACTION_LOGIN, + ACTION_LOGOUT, + ACTION_ADD_SECRETS, + ACTION_UPDATE_SECRETS, + ACTION_READ_SECRETS, + ACTION_DELETE_SECRETS + ] }, user: { type: Schema.Types.ObjectId, diff --git a/backend/src/ee/models/log.ts b/backend/src/ee/models/log.ts index b9eadc016d..47be2e58f9 100644 --- a/backend/src/ee/models/log.ts +++ b/backend/src/ee/models/log.ts @@ -1,5 +1,7 @@ import { Schema, model, Types } from 'mongoose'; import { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_READ_SECRETS, @@ -29,6 +31,8 @@ const logSchema = new Schema( actionNames: { type: [String], enum: [ + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_READ_SECRETS, diff --git a/backend/src/ee/services/EELogService.ts b/backend/src/ee/services/EELogService.ts index c1b2da6fb0..bbe03e09e7 100644 --- a/backend/src/ee/services/EELogService.ts +++ b/backend/src/ee/services/EELogService.ts @@ -1,14 +1,12 @@ import { Types } from 'mongoose'; import { - Log, - Action, IAction } from '../models'; import { createLogHelper } from '../helpers/log'; import { - createActionSecretHelper + createActionHelper } from '../helpers/action'; import EELicenseService from './EELicenseService'; @@ -33,8 +31,8 @@ class EELogService { channel, ipAddress }: { - userId: string; - workspaceId: string; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; actions: IAction[]; channel: string; ipAddress: string; @@ -50,26 +48,26 @@ class EELogService { } /** - * Create an (audit) action for secrets including - * add, delete, update, and read actions. + * Create an (audit) action * @param {Object} obj * @param {String} obj.name - name of action - * @param {ObjectId[]} obj.secretIds - secret ids + * @param {Types.ObjectId} obj.userId - id of user associated with the action + * @param {Types.ObjectId} obj.workspaceId - id of workspace associated with the action + * @param {ObjectId[]} obj.secretIds - ids of secrets associated with the action * @returns {Action} action - new action */ - static async createActionSecret({ + static async createAction({ name, userId, workspaceId, secretIds }: { name: string; - userId: string; - workspaceId: string; - secretIds: Types.ObjectId[]; + userId: Types.ObjectId; + workspaceId?: Types.ObjectId; + secretIds?: Types.ObjectId[]; }) { - if (!EELicenseService.isLicenseValid) return null; - return await createActionSecretHelper({ + return await createActionHelper({ name, userId, workspaceId, diff --git a/backend/src/helpers/secret.ts b/backend/src/helpers/secret.ts index a16d114c61..9466cf7aad 100644 --- a/backend/src/helpers/secret.ts +++ b/backend/src/helpers/secret.ts @@ -406,10 +406,10 @@ const v2PushSecrets = async ({ secretIds: toDelete }); - const deleteAction = await EELogService.createActionSecret({ + const deleteAction = await EELogService.createAction({ name: ACTION_DELETE_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(userId), secretIds: toDelete }); @@ -499,10 +499,10 @@ const v2PushSecrets = async ({ }) }); - const updateAction = await EELogService.createActionSecret({ + const updateAction = await EELogService.createAction({ name: ACTION_UPDATE_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: toUpdate.map((u) => u._id) }); @@ -536,10 +536,10 @@ const v2PushSecrets = async ({ }) }); - const addAction = await EELogService.createActionSecret({ + const addAction = await EELogService.createAction({ name: ACTION_ADD_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: newSecrets.map((n) => n._id) }); addAction && actions.push(addAction); @@ -553,8 +553,8 @@ const v2PushSecrets = async ({ // (EE) create (audit) log if (actions.length > 0) { await EELogService.createLog({ - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), actions, channel, ipAddress @@ -645,16 +645,16 @@ const pullSecrets = async ({ environment }) - const readAction = await EELogService.createActionSecret({ + const readAction = await EELogService.createAction({ name: ACTION_READ_SECRETS, - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), secretIds: secrets.map((n: any) => n._id) }); readAction && await EELogService.createLog({ - userId, - workspaceId, + userId: new Types.ObjectId(userId), + workspaceId: new Types.ObjectId(workspaceId), actions: [readAction], channel, ipAddress diff --git a/backend/src/models/LoginSRPDetail.ts b/backend/src/models/LoginSRPDetail.ts deleted file mode 100644 index f65e779c39..0000000000 --- a/backend/src/models/LoginSRPDetail.ts +++ /dev/null @@ -1,23 +0,0 @@ -import mongoose, { Schema, model } from 'mongoose'; - -const LoginSRPDetailSchema = new Schema( - { - clientPublicKey: { - type: String, - required: true - }, - email: { - type: String, - required: true, - unique: true - }, - serverBInt: { type: mongoose.Schema.Types.Buffer }, - expireAt: { type: Date } - } -); - -const LoginSRPDetail = model('LoginSRPDetail', LoginSRPDetailSchema); - -// LoginSRPDetailSchema.index({ "expireAt": 1 }, { expireAfterSeconds: 0 }); - -export default LoginSRPDetail; diff --git a/backend/src/models/index.ts b/backend/src/models/index.ts index 72ffca6077..ac5f102c0d 100644 --- a/backend/src/models/index.ts +++ b/backend/src/models/index.ts @@ -16,6 +16,7 @@ import UserAction, { IUserAction } from './userAction'; import Workspace, { IWorkspace } from './workspace'; import ServiceTokenData, { IServiceTokenData } from './serviceTokenData'; import APIKeyData, { IAPIKeyData } from './apiKeyData'; +import LoginSRPDetail, { ILoginSRPDetail } from './loginSRPDetail'; export { BackupPrivateKey, @@ -53,5 +54,7 @@ export { ServiceTokenData, IServiceTokenData, APIKeyData, - IAPIKeyData + IAPIKeyData, + LoginSRPDetail, + ILoginSRPDetail }; diff --git a/backend/src/models/loginSRPDetail.ts b/backend/src/models/loginSRPDetail.ts new file mode 100644 index 0000000000..6f3b579bd3 --- /dev/null +++ b/backend/src/models/loginSRPDetail.ts @@ -0,0 +1,29 @@ +import mongoose, { Schema, model, Types } from 'mongoose'; + +export interface ILoginSRPDetail { + _id: Types.ObjectId; + clientPublicKey: string; + email: string; + serverBInt: mongoose.Schema.Types.Buffer; + expireAt: Date; +} + +const loginSRPDetailSchema = new Schema( + { + clientPublicKey: { + type: String, + required: true + }, + email: { + type: String, + required: true, + unique: true + }, + serverBInt: { type: mongoose.Schema.Types.Buffer }, + expireAt: { type: Date } + } +); + +const LoginSRPDetail = model('LoginSRPDetail', loginSRPDetailSchema); + +export default LoginSRPDetail; diff --git a/backend/src/models/user.ts b/backend/src/models/user.ts index 7ea988c9dc..ba9a59bc55 100644 --- a/backend/src/models/user.ts +++ b/backend/src/models/user.ts @@ -12,6 +12,7 @@ export interface IUser { salt?: string; verifier?: string; refreshVersion?: number; + seenIps: [string]; } const userSchema = new Schema( @@ -54,7 +55,8 @@ const userSchema = new Schema( type: Number, default: 0, select: false - } + }, + seenIps: [String] }, { timestamps: true diff --git a/backend/src/variables/action.ts b/backend/src/variables/action.ts index 512eb8e8d7..682b357a75 100644 --- a/backend/src/variables/action.ts +++ b/backend/src/variables/action.ts @@ -1,9 +1,13 @@ +const ACTION_LOGIN = 'login'; +const ACTION_LOGOUT = 'logout'; const ACTION_ADD_SECRETS = 'addSecrets'; const ACTION_DELETE_SECRETS = 'deleteSecrets'; const ACTION_UPDATE_SECRETS = 'updateSecrets'; const ACTION_READ_SECRETS = 'readSecrets'; export { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_DELETE_SECRETS, ACTION_UPDATE_SECRETS, diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index dc1ce6f783..fafab69392 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -35,6 +35,8 @@ import { import { SECRET_SHARED, SECRET_PERSONAL } from './secret'; import { EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS } from './event'; import { + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_DELETE_SECRETS, @@ -75,6 +77,8 @@ export { INTEGRATION_FLYIO_API_URL, EVENT_PUSH_SECRETS, EVENT_PULL_SECRETS, + ACTION_LOGIN, + ACTION_LOGOUT, ACTION_ADD_SECRETS, ACTION_UPDATE_SECRETS, ACTION_DELETE_SECRETS,