mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 14:27:59 +00:00
Upgraded JWT invalidation/session logic to separate TokenVersion model.
This commit is contained in:
@@ -13,7 +13,8 @@ import { createToken, issueAuthTokens, clearTokens } from '../../helpers/auth';
|
|||||||
import { checkUserDevice } from '../../helpers/user';
|
import { checkUserDevice } from '../../helpers/user';
|
||||||
import {
|
import {
|
||||||
ACTION_LOGIN,
|
ACTION_LOGIN,
|
||||||
ACTION_LOGOUT
|
ACTION_LOGOUT,
|
||||||
|
AUTH_MODE_JWT
|
||||||
} from '../../variables';
|
} from '../../variables';
|
||||||
import {
|
import {
|
||||||
BadRequestError,
|
BadRequestError,
|
||||||
@@ -127,7 +128,11 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
userAgent: req.headers['user-agent'] ?? ''
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
const tokens = await issueAuthTokens({
|
||||||
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
@@ -181,7 +186,10 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
*/
|
*/
|
||||||
export const logout = async (req: Request, res: Response) => {
|
export const logout = async (req: Request, res: Response) => {
|
||||||
try {
|
try {
|
||||||
await clearTokens(req.user._id);
|
|
||||||
|
if (req.authData.authMode === AUTH_MODE_JWT && req.authData.authPayload instanceof User && req.authData.tokenVersionId) {
|
||||||
|
await clearTokens(req.authData.tokenVersionId)
|
||||||
|
}
|
||||||
|
|
||||||
// clear httpOnly cookie
|
// clear httpOnly cookie
|
||||||
res.cookie('jid', '', {
|
res.cookie('jid', '', {
|
||||||
@@ -216,6 +224,21 @@ export const logout = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const revokeAllSessions = async (req: Request, res: Response) => {
|
||||||
|
await TokenVersion.updateMany({
|
||||||
|
user: req.user._id
|
||||||
|
}, {
|
||||||
|
$inc: {
|
||||||
|
refreshVersion: 1,
|
||||||
|
accessVersion: 1
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(200).send({
|
||||||
|
message: 'Successfully revoked all sessions.'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return user is authenticated
|
* Return user is authenticated
|
||||||
* @param req
|
* @param req
|
||||||
@@ -254,12 +277,7 @@ export const getNewToken = async (req: Request, res: Response) => {
|
|||||||
if (!user?.publicKey)
|
if (!user?.publicKey)
|
||||||
throw new Error('Failed to authenticate not fully set up account');
|
throw new Error('Failed to authenticate not fully set up account');
|
||||||
|
|
||||||
const tokenVersion = await TokenVersion.findOne({
|
const tokenVersion = await TokenVersion.findById(decodedToken.tokenVersionId);
|
||||||
_id: decodedToken.tokenVersionId,
|
|
||||||
user: user._id
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('tokenVersion: ', tokenVersion);
|
|
||||||
|
|
||||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||||
message: 'Failed to validate refresh token'
|
message: 'Failed to validate refresh token'
|
||||||
@@ -272,6 +290,7 @@ export const getNewToken = async (req: Request, res: Response) => {
|
|||||||
const token = createToken({
|
const token = createToken({
|
||||||
payload: {
|
payload: {
|
||||||
userId: decodedToken.userId,
|
userId: decodedToken.userId,
|
||||||
|
tokenVersionId: tokenVersion._id.toString(),
|
||||||
accessVersion: tokenVersion.refreshVersion
|
accessVersion: tokenVersion.refreshVersion
|
||||||
},
|
},
|
||||||
expiresIn: await getJwtAuthLifetime(),
|
expiresIn: await getJwtAuthLifetime(),
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ import {
|
|||||||
getHttpsEnabled
|
getHttpsEnabled
|
||||||
} from '../../config';
|
} from '../../config';
|
||||||
|
|
||||||
// note: move this out
|
|
||||||
import path from 'path';
|
|
||||||
import fs from 'fs';
|
|
||||||
|
|
||||||
declare module 'jsonwebtoken' {
|
declare module 'jsonwebtoken' {
|
||||||
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
export interface UserIDJwtPayload extends jwt.JwtPayload {
|
||||||
userId: string;
|
userId: string;
|
||||||
@@ -160,7 +156,11 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
const tokens = await issueAuthTokens({
|
||||||
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
@@ -301,7 +301,11 @@ export const verifyMfaToken = async (req: Request, res: Response) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
const tokens = await issueAuthTokens({
|
||||||
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
|
|||||||
@@ -116,7 +116,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({
|
const tokens = await issueAuthTokens({
|
||||||
userId: user._id.toString()
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
@@ -247,7 +249,9 @@ export const completeAccountInvite = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({
|
const tokens = await issueAuthTokens({
|
||||||
userId: user._id.toString()
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
|
|||||||
@@ -183,12 +183,12 @@ export const login2 = async (req: Request, res: Response) => {
|
|||||||
userAgent: req.headers['user-agent'] ?? ''
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('logged in, issue tokens');
|
|
||||||
console.log('ip: ', req.ip);
|
|
||||||
console.log('userAgent: ', req.headers['user-agent']);
|
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({ userId: user._id.toString() });
|
const tokens = await issueAuthTokens({
|
||||||
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
|
});
|
||||||
|
|
||||||
// store (refresh) token in httpOnly cookie
|
// store (refresh) token in httpOnly cookie
|
||||||
res.cookie('jid', tokens.refreshToken, {
|
res.cookie('jid', tokens.refreshToken, {
|
||||||
|
|||||||
@@ -137,7 +137,9 @@ export const completeAccountSignup = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const tokens = await issueAuthTokens({
|
const tokens = await issueAuthTokens({
|
||||||
userId: user._id.toString()
|
userId: user._id,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.headers['user-agent'] ?? ''
|
||||||
});
|
});
|
||||||
|
|
||||||
token = tokens.token;
|
token = tokens.token;
|
||||||
|
|||||||
+46
-30
@@ -7,7 +7,8 @@ import {
|
|||||||
ServiceTokenData,
|
ServiceTokenData,
|
||||||
ServiceAccount,
|
ServiceAccount,
|
||||||
APIKeyData,
|
APIKeyData,
|
||||||
TokenVersion
|
TokenVersion,
|
||||||
|
ITokenVersion
|
||||||
} from '../models';
|
} from '../models';
|
||||||
import {
|
import {
|
||||||
AccountNotFoundError,
|
AccountNotFoundError,
|
||||||
@@ -108,35 +109,32 @@ export const getAuthUserPayload = async ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
const user = await User.findOne({
|
const user = await User.findOne({
|
||||||
_id: decodedToken.userId
|
_id: new Types.ObjectId(decodedToken.userId)
|
||||||
}).select('+publicKey +accessVersion');
|
}).select('+publicKey +accessVersion');
|
||||||
|
|
||||||
if (!user) throw AccountNotFoundError({ message: 'Failed to find user' });
|
if (!user) throw AccountNotFoundError({ message: 'Failed to find user' });
|
||||||
|
|
||||||
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate user with partially set up account' });
|
if (!user?.publicKey) throw UnauthorizedRequestError({ message: 'Failed to authenticate user with partially set up account' });
|
||||||
|
|
||||||
console.log('getAuthUserPayload');
|
const tokenVersion = await TokenVersion.findOneAndUpdate({
|
||||||
|
_id: new Types.ObjectId(decodedToken.tokenVersionId),
|
||||||
const tokenVersion = await TokenVersion.findOne({
|
|
||||||
_id: decodedToken.tokenVersionId,
|
|
||||||
user: user._id
|
user: user._id
|
||||||
|
}, {
|
||||||
|
lastUsed: new Date()
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('tokenVersion: ', tokenVersion);
|
|
||||||
|
|
||||||
if (!tokenVersion) throw UnauthorizedRequestError({
|
if (!tokenVersion) throw UnauthorizedRequestError({
|
||||||
message: 'Failed to validate access token'
|
message: 'Failed to validate access token'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (decodedToken.accessVersion !== tokenVersion.accessVersion) {
|
if (decodedToken.accessVersion !== tokenVersion.accessVersion) throw UnauthorizedRequestError({
|
||||||
console.log('incorrect version');
|
message: 'Failed to validate access token'
|
||||||
|
});
|
||||||
|
|
||||||
throw UnauthorizedRequestError({
|
return ({
|
||||||
message: 'Failed to validate access token'
|
user,
|
||||||
});
|
tokenVersionId: tokenVersion._id
|
||||||
}
|
});
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -277,17 +275,36 @@ export const getAuthAPIKeyPayload = async ({
|
|||||||
* @return {String} obj.token - issued JWT token
|
* @return {String} obj.token - issued JWT token
|
||||||
* @return {String} obj.refreshToken - issued refresh token
|
* @return {String} obj.refreshToken - issued refresh token
|
||||||
*/
|
*/
|
||||||
export const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
export const issueAuthTokens = async ({
|
||||||
|
userId,
|
||||||
// TODO: create tokenVersion here
|
ip,
|
||||||
// TODO: include some kind of (channel) name here
|
userAgent
|
||||||
|
}: {
|
||||||
|
userId: Types.ObjectId;
|
||||||
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
|
}) => {
|
||||||
|
let tokenVersion: ITokenVersion | null;
|
||||||
|
|
||||||
const tokenVersion = await new TokenVersion({
|
// continue with (session) token version matching existing ip and user agent
|
||||||
user: new Types.ObjectId(userId),
|
tokenVersion = await TokenVersion.findOne({
|
||||||
name: '', // improve to channel
|
user: userId,
|
||||||
refreshVersion: 0,
|
ip,
|
||||||
accessVersion: 0
|
userAgent
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!tokenVersion) {
|
||||||
|
// case: no existing ip and user agent exists
|
||||||
|
// -> create new (session) token version for ip and user agent
|
||||||
|
tokenVersion = await new TokenVersion({
|
||||||
|
user: userId,
|
||||||
|
refreshVersion: 0,
|
||||||
|
accessVersion: 0,
|
||||||
|
ip,
|
||||||
|
userAgent,
|
||||||
|
lastUsed: new Date()
|
||||||
|
}).save();
|
||||||
|
}
|
||||||
|
|
||||||
// issue tokens
|
// issue tokens
|
||||||
const token = createToken({
|
const token = createToken({
|
||||||
@@ -321,12 +338,11 @@ export const issueAuthTokens = async ({ userId }: { userId: string }) => {
|
|||||||
* @param {Object} obj
|
* @param {Object} obj
|
||||||
* @param {String} obj.userId - id of user whose tokens are cleared.
|
* @param {String} obj.userId - id of user whose tokens are cleared.
|
||||||
*/
|
*/
|
||||||
export const clearTokens = async (userId: Types.ObjectId): Promise<void> => {
|
export const clearTokens = async (tokenVersionId: Types.ObjectId): Promise<void> => {
|
||||||
// increment refreshVersion on user by 1
|
// increment refreshVersion on user by 1
|
||||||
|
|
||||||
// change this
|
await TokenVersion.findOneAndUpdate({
|
||||||
await User.findOneAndUpdate({
|
_id: tokenVersionId
|
||||||
_id: userId
|
|
||||||
}, {
|
}, {
|
||||||
$inc: {
|
$inc: {
|
||||||
refreshVersion: 1,
|
refreshVersion: 1,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
IServiceAccount,
|
IServiceAccount,
|
||||||
@@ -10,4 +11,5 @@ export interface AuthData {
|
|||||||
authChannel: string;
|
authChannel: string;
|
||||||
authIP: string;
|
authIP: string;
|
||||||
authUserAgent: string;
|
authUserAgent: string;
|
||||||
|
tokenVersionId?: Types.ObjectId;
|
||||||
}
|
}
|
||||||
@@ -71,10 +71,12 @@ const requireAuth = ({
|
|||||||
req.user = authPayload;
|
req.user = authPayload;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
authPayload = await getAuthUserPayload({
|
const { user, tokenVersionId } = await getAuthUserPayload({
|
||||||
authTokenValue
|
authTokenValue
|
||||||
});
|
});
|
||||||
req.user = authPayload;
|
authPayload = user;
|
||||||
|
req.user = user;
|
||||||
|
req.tokenVersionId = tokenVersionId;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +91,8 @@ const requireAuth = ({
|
|||||||
authPayload, // User, ServiceAccount, ServiceTokenData
|
authPayload, // User, ServiceAccount, ServiceTokenData
|
||||||
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
|
authChannel: getChannelFromUserAgent(req.headers['user-agent']),
|
||||||
authIP: req.ip,
|
authIP: req.ip,
|
||||||
authUserAgent: req.headers['user-agent'] ?? 'other'
|
authUserAgent: req.headers['user-agent'] ?? 'other',
|
||||||
|
tokenVersionId: req.tokenVersionId
|
||||||
}
|
}
|
||||||
|
|
||||||
return next();
|
return next();
|
||||||
|
|||||||
@@ -2,9 +2,11 @@ import { Schema, model, Types, Document } from 'mongoose';
|
|||||||
|
|
||||||
export interface ITokenVersion extends Document {
|
export interface ITokenVersion extends Document {
|
||||||
user: Types.ObjectId;
|
user: Types.ObjectId;
|
||||||
name: string;
|
ip: string;
|
||||||
|
userAgent: string;
|
||||||
refreshVersion: number;
|
refreshVersion: number;
|
||||||
accessVersion: number;
|
accessVersion: number;
|
||||||
|
lastUsed: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tokenVersionSchema = new Schema<ITokenVersion>(
|
const tokenVersionSchema = new Schema<ITokenVersion>(
|
||||||
@@ -14,7 +16,11 @@ const tokenVersionSchema = new Schema<ITokenVersion>(
|
|||||||
ref: 'User',
|
ref: 'User',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
name: {
|
ip: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
userAgent: {
|
||||||
type: String,
|
type: String,
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
@@ -25,6 +31,10 @@ const tokenVersionSchema = new Schema<ITokenVersion>(
|
|||||||
accessVersion: {
|
accessVersion: {
|
||||||
type: Number,
|
type: Number,
|
||||||
required: true
|
required: true
|
||||||
|
},
|
||||||
|
lastUsed: {
|
||||||
|
type: Date,
|
||||||
|
required: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ router.post(
|
|||||||
authController.checkAuth
|
authController.checkAuth
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/redirect/google',
|
'/redirect/google',
|
||||||
authLimiter,
|
authLimiter,
|
||||||
@@ -53,12 +51,20 @@ router.get(
|
|||||||
scope: ['profile', 'email'],
|
scope: ['profile', 'email'],
|
||||||
session: false,
|
session: false,
|
||||||
}),
|
}),
|
||||||
)
|
);
|
||||||
|
|
||||||
router.get(
|
router.get(
|
||||||
'/callback/google',
|
'/callback/google',
|
||||||
passport.authenticate('google', { failureRedirect: '/login/provider/error', session: false }),
|
passport.authenticate('google', { failureRedirect: '/login/provider/error', session: false }),
|
||||||
authController.handleAuthProviderCallback,
|
authController.handleAuthProviderCallback,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
router.delete(
|
||||||
|
'/sessions',
|
||||||
|
requireAuth({
|
||||||
|
acceptedAuthModes: [AUTH_MODE_JWT]
|
||||||
|
}),
|
||||||
|
authController.revokeAllSessions
|
||||||
|
);
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
|
|||||||
Vendored
+2
@@ -1,4 +1,5 @@
|
|||||||
import * as express from 'express';
|
import * as express from 'express';
|
||||||
|
import { Types } from 'mongoose';
|
||||||
import {
|
import {
|
||||||
IUser,
|
IUser,
|
||||||
IServiceAccount,
|
IServiceAccount,
|
||||||
@@ -39,6 +40,7 @@ declare global {
|
|||||||
serviceTokenData: any;
|
serviceTokenData: any;
|
||||||
apiKeyData: any;
|
apiKeyData: any;
|
||||||
query?: any;
|
query?: any;
|
||||||
|
tokenVersionId?: Types.ObjectId;
|
||||||
authData: AuthData;
|
authData: AuthData;
|
||||||
requestData: {
|
requestData: {
|
||||||
[key: string]: string
|
[key: string]: string
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
export {
|
export {
|
||||||
useGetAuthToken,
|
useGetAuthToken,
|
||||||
useSendMfaToken,
|
useSendMfaToken,
|
||||||
useVerifyMfaToken} from './queries'
|
useVerifyMfaToken,
|
||||||
|
useRevokeAllSessions
|
||||||
|
} from './queries'
|
||||||
|
|||||||
@@ -49,3 +49,12 @@ export const useGetAuthToken = () =>
|
|||||||
onSuccess: (data) => setAuthToken(data.token),
|
onSuccess: (data) => setAuthToken(data.token),
|
||||||
retry: 0
|
retry: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const useRevokeAllSessions = () => {
|
||||||
|
return useMutation({
|
||||||
|
mutationFn: async () => {
|
||||||
|
const { data } = await apiRequest.delete('/api/v1/auth/sessions');
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import Head from 'next/head';
|
import Head from 'next/head';
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { faCheck, faPlus, faX } from '@fortawesome/free-solid-svg-icons';
|
import { faCheck, faPlus, faX, faBan } from '@fortawesome/free-solid-svg-icons';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
import Button from '@app/components/basic/buttons/Button';
|
import Button from '@app/components/basic/buttons/Button';
|
||||||
@@ -18,6 +18,9 @@ import { SecuritySection } from '@app/views/Settings/PersonalSettingsPage/Securi
|
|||||||
import AddApiKeyDialog from '../../../components/basic/dialog/AddApiKeyDialog';
|
import AddApiKeyDialog from '../../../components/basic/dialog/AddApiKeyDialog';
|
||||||
import getAPIKeys from '../../api/apiKey/getAPIKeys';
|
import getAPIKeys from '../../api/apiKey/getAPIKeys';
|
||||||
import getUser from '../../api/user/getUser';
|
import getUser from '../../api/user/getUser';
|
||||||
|
import {
|
||||||
|
useRevokeAllSessions
|
||||||
|
} from '@app/hooks/api';
|
||||||
|
|
||||||
export default function PersonalSettings() {
|
export default function PersonalSettings() {
|
||||||
const [personalEmail, setPersonalEmail] = useState('');
|
const [personalEmail, setPersonalEmail] = useState('');
|
||||||
@@ -34,6 +37,8 @@ export default function PersonalSettings() {
|
|||||||
const [backupKeyError, setBackupKeyError] = useState(false);
|
const [backupKeyError, setBackupKeyError] = useState(false);
|
||||||
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false);
|
const [isAddApiKeyDialogOpen, setIsAddApiKeyDialogOpen] = useState(false);
|
||||||
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
const [apiKeys, setApiKeys] = useState<any[]>([]);
|
||||||
|
|
||||||
|
const revokeAllSessions = useRevokeAllSessions();
|
||||||
|
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -254,6 +259,28 @@ export default function PersonalSettings() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mb-6 mt-2 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pb-6 pt-2">
|
||||||
|
<div className="my-4 flex w-full flex-row justify-between">
|
||||||
|
<p className="text-xl font-semibold w-full">
|
||||||
|
Sessions
|
||||||
|
</p>
|
||||||
|
<div className="w-40">
|
||||||
|
<Button
|
||||||
|
text="Revoke all"
|
||||||
|
onButtonPressed={async () => {
|
||||||
|
await revokeAllSessions.mutateAsync();
|
||||||
|
router.push('/login');
|
||||||
|
}}
|
||||||
|
color="mineshaft"
|
||||||
|
icon={faBan}
|
||||||
|
size="md"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p className="mb-5 text-sm text-mineshaft-300">
|
||||||
|
Logging into Infisical via browser or CLI creates a session. Revoking all sessions logs your account out all active sessions across all browsers and CLIs.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-2 mb-6 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pt-5 pb-6">
|
<div className="mt-2 mb-6 flex w-full flex-col items-start rounded-md bg-white/5 px-6 pt-5 pb-6">
|
||||||
<div className="flex w-full max-w-5xl flex-row items-center justify-between">
|
<div className="flex w-full max-w-5xl flex-row items-center justify-between">
|
||||||
|
|||||||
Reference in New Issue
Block a user