2018-05-02 20:40:50 +03:00
|
|
|
//
|
2019-10-03 04:41:16 +03:00
|
|
|
// Copyright (c) Microsoft.
|
2018-05-02 20:40:50 +03:00
|
|
|
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
|
|
|
//
|
|
|
|
|
2023-06-12 03:42:22 +03:00
|
|
|
import { NextFunction, Response, Router } from 'express';
|
2020-04-19 23:52:36 +03:00
|
|
|
import asyncHandler from 'express-async-handler';
|
2021-04-27 23:58:45 +03:00
|
|
|
const router: Router = Router();
|
2019-08-07 02:53:50 +03:00
|
|
|
|
2024-01-03 22:44:13 +03:00
|
|
|
import { getProviders } from '../../lib/transitional';
|
|
|
|
import { PersonalAccessToken } from '../../business/entities/token/token';
|
2021-04-10 02:36:15 +03:00
|
|
|
import { ReposAppRequest } from '../../interfaces';
|
2019-08-07 02:53:50 +03:00
|
|
|
|
2023-06-12 03:42:22 +03:00
|
|
|
type ResponseWithNewKey = Response & {
|
|
|
|
newKey: string;
|
|
|
|
};
|
|
|
|
|
2019-08-07 02:53:50 +03:00
|
|
|
interface IPersonalAccessTokenForDisplay {
|
|
|
|
active: boolean;
|
|
|
|
expired: boolean;
|
|
|
|
expires: string;
|
|
|
|
identifier: string;
|
|
|
|
description: string;
|
|
|
|
apis: string[];
|
|
|
|
tokenEntity: PersonalAccessToken;
|
|
|
|
}
|
|
|
|
|
2022-10-08 01:25:28 +03:00
|
|
|
export interface IRequestForSettingsPersonalAccessTokens extends ReposAppRequest {
|
2019-08-07 02:53:50 +03:00
|
|
|
personalAccessTokens?: IPersonalAccessTokenForDisplay[];
|
|
|
|
}
|
2018-05-02 20:40:50 +03:00
|
|
|
|
|
|
|
const serviceName = 'repos-pat';
|
2019-08-07 02:53:50 +03:00
|
|
|
const tokenExpirationMs = 1000 * 60 * 60 * 24 * 365; // 365 days
|
|
|
|
|
2022-10-07 09:59:30 +03:00
|
|
|
function translateTableToEntities(
|
|
|
|
personalAccessTokens: PersonalAccessToken[]
|
|
|
|
): IPersonalAccessTokenForDisplay[] {
|
|
|
|
return personalAccessTokens.map((pat) => {
|
2019-08-07 02:53:50 +03:00
|
|
|
// So that we do not share the hashed key with the user, we
|
|
|
|
// build a hash of that and the timestamp to offer a single-version
|
|
|
|
// tag to use for delete operations, etc.
|
|
|
|
const displayToken: IPersonalAccessTokenForDisplay = {
|
|
|
|
active: pat.active,
|
|
|
|
expired: pat.isExpired(),
|
|
|
|
expires: pat.expires ? pat.expires.toDateString() : null,
|
|
|
|
description: pat.description,
|
|
|
|
apis: pat.scopes ? pat.scopes.split(',') : [],
|
|
|
|
identifier: pat.getIdentifier(),
|
|
|
|
tokenEntity: pat,
|
|
|
|
};
|
|
|
|
return displayToken;
|
|
|
|
});
|
2018-05-02 20:40:50 +03:00
|
|
|
}
|
|
|
|
|
2023-06-12 03:42:22 +03:00
|
|
|
function getPersonalAccessTokens(req: ReposAppRequest, res: Response, next: NextFunction) {
|
2021-03-19 19:20:39 +03:00
|
|
|
const providers = getProviders(req);
|
2019-08-07 02:53:50 +03:00
|
|
|
const tokenProvider = providers.tokenProvider;
|
|
|
|
const corporateId = req.individualContext.corporateIdentity.id;
|
2022-10-07 09:59:30 +03:00
|
|
|
tokenProvider
|
|
|
|
.queryTokensForCorporateId(corporateId)
|
|
|
|
.then((tokens) => {
|
|
|
|
req['personalAccessTokens'] = translateTableToEntities(tokens);
|
|
|
|
return next();
|
|
|
|
})
|
|
|
|
.catch((error) => {
|
|
|
|
return next(error);
|
|
|
|
});
|
2018-05-02 20:40:50 +03:00
|
|
|
}
|
|
|
|
|
2019-08-07 02:53:50 +03:00
|
|
|
function view(req: IRequestForSettingsPersonalAccessTokens, res) {
|
2018-05-02 20:40:50 +03:00
|
|
|
const personalAccessTokens = req.personalAccessTokens;
|
2019-04-06 00:45:34 +03:00
|
|
|
req.individualContext.webContext.render({
|
|
|
|
view: 'settings/personalAccessTokens',
|
|
|
|
title: 'Personal access tokens',
|
|
|
|
state: {
|
2019-08-07 02:53:50 +03:00
|
|
|
personalAccessTokens,
|
2019-04-06 00:45:34 +03:00
|
|
|
newKey: res.newKey,
|
|
|
|
isPreviewUser: true, //req.isPreviewUser,
|
|
|
|
},
|
2018-05-02 20:40:50 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
router.use(getPersonalAccessTokens);
|
|
|
|
|
|
|
|
router.get('/', view);
|
|
|
|
|
2023-06-12 03:42:22 +03:00
|
|
|
function createToken(req: ReposAppRequest, res: ResponseWithNewKey, next: NextFunction) {
|
2021-03-19 19:20:39 +03:00
|
|
|
const providers = getProviders(req);
|
2019-08-07 02:53:50 +03:00
|
|
|
const tokenProvider = providers.tokenProvider;
|
2018-05-02 20:40:50 +03:00
|
|
|
const insights = req.insights;
|
|
|
|
const description = req.body.description;
|
|
|
|
if (!description) {
|
2022-10-08 01:25:28 +03:00
|
|
|
return next(new Error('A description is required to create a new Personal Access Token'));
|
2018-05-02 20:40:50 +03:00
|
|
|
}
|
2019-08-07 02:53:50 +03:00
|
|
|
const corporateId = req.individualContext.corporateIdentity.id;
|
|
|
|
const token = PersonalAccessToken.CreateNewToken();
|
|
|
|
token.corporateId = corporateId;
|
|
|
|
token.description = description;
|
2018-05-02 20:40:50 +03:00
|
|
|
const now = new Date();
|
2019-08-07 02:53:50 +03:00
|
|
|
token.expires = new Date(now.getTime() + tokenExpirationMs);
|
|
|
|
token.source = serviceName;
|
|
|
|
token.scopes = 'extension,links';
|
2018-05-02 20:40:50 +03:00
|
|
|
insights.trackEvent({
|
|
|
|
name: 'ReposCreateTokenStart',
|
|
|
|
properties: {
|
2019-08-07 02:53:50 +03:00
|
|
|
id: corporateId,
|
2018-05-02 20:40:50 +03:00
|
|
|
description: description,
|
|
|
|
},
|
|
|
|
});
|
2022-10-07 09:59:30 +03:00
|
|
|
tokenProvider
|
|
|
|
.saveNewToken(token)
|
|
|
|
.then((ok) => {
|
|
|
|
insights.trackEvent({
|
|
|
|
name: 'ReposCreateTokenFinish',
|
|
|
|
properties: {
|
|
|
|
id: corporateId,
|
|
|
|
description: description,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
const newKey = token.getPrivateKey();
|
|
|
|
getPersonalAccessTokens(req, res, () => {
|
|
|
|
res.newKey = newKey;
|
|
|
|
return view(req, res);
|
|
|
|
});
|
|
|
|
})
|
|
|
|
.catch((insertError) => {
|
|
|
|
insights.trackEvent({
|
|
|
|
name: 'ReposCreateTokenFailure',
|
|
|
|
properties: {
|
|
|
|
id: corporateId,
|
|
|
|
description: description,
|
|
|
|
},
|
|
|
|
});
|
|
|
|
return next(insertError);
|
2021-10-08 01:23:50 +03:00
|
|
|
});
|
2018-05-02 20:40:50 +03:00
|
|
|
}
|
|
|
|
|
2019-04-06 00:45:34 +03:00
|
|
|
router.post('/create', createToken);
|
|
|
|
router.post('/extension', createToken);
|
2018-05-02 20:40:50 +03:00
|
|
|
|
2022-10-07 09:59:30 +03:00
|
|
|
router.post(
|
|
|
|
'/delete',
|
2023-06-12 03:42:22 +03:00
|
|
|
asyncHandler(async (req: IRequestForSettingsPersonalAccessTokens, res: Response, next: NextFunction) => {
|
2022-10-08 01:25:28 +03:00
|
|
|
const providers = getProviders(req);
|
|
|
|
const tokenProvider = providers.tokenProvider;
|
|
|
|
const revokeAll = req.body.revokeAll === '1';
|
|
|
|
const revokeIdentifier = req.body.revoke;
|
|
|
|
const personalAccessTokens = req.personalAccessTokens;
|
|
|
|
for (const pat of personalAccessTokens) {
|
|
|
|
const token = pat.tokenEntity;
|
|
|
|
if (revokeAll || pat.identifier === revokeIdentifier) {
|
|
|
|
token.active = false;
|
|
|
|
await tokenProvider.updateToken(token);
|
2022-10-07 09:59:30 +03:00
|
|
|
}
|
2018-05-02 20:40:50 +03:00
|
|
|
}
|
2022-10-08 01:25:28 +03:00
|
|
|
return res.redirect('/settings/security/tokens');
|
|
|
|
})
|
2022-10-07 09:59:30 +03:00
|
|
|
);
|
2018-05-02 20:40:50 +03:00
|
|
|
|
2020-07-29 02:24:21 +03:00
|
|
|
export default router;
|