@verdaccio/auth 7.0.0-next.3 → 7.0.0-next.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +30 -0
- package/build/auth.d.ts +7 -26
- package/build/auth.js +43 -31
- package/build/auth.js.map +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +12 -0
- package/build/index.js.map +1 -1
- package/build/signature-legacy.d.ts +5 -0
- package/build/signature-legacy.js +60 -0
- package/build/signature-legacy.js.map +1 -0
- package/build/signature.d.ts +4 -0
- package/build/signature.js +60 -0
- package/build/signature.js.map +1 -0
- package/build/types.d.ts +34 -0
- package/build/types.js +13 -0
- package/build/types.js.map +1 -0
- package/build/utils.d.ts +5 -15
- package/build/utils.js +19 -9
- package/build/utils.js.map +1 -1
- package/package.json +12 -10
- package/src/auth.ts +66 -59
- package/src/index.ts +1 -0
- package/src/signature-legacy.ts +66 -0
- package/src/signature.ts +66 -0
- package/src/types.ts +46 -0
- package/src/utils.ts +37 -31
- package/test/auth.spec.ts +561 -40
- package/test/helper/plugin.ts +8 -0
- package/test/partials/plugin/verdaccio-access-ok/access.js +9 -0
- package/test/partials/plugin/verdaccio-access-ok/package.json +5 -0
- package/test/partials/plugin/verdaccio-adduser/adduser.js +25 -0
- package/test/partials/plugin/verdaccio-adduser/package.json +5 -0
- package/test/partials/plugin/verdaccio-adduser-legacy/adduser.js +25 -0
- package/test/partials/plugin/verdaccio-adduser-legacy/package.json +5 -0
- package/test/partials/plugin/verdaccio-change-password/change.js +32 -0
- package/test/partials/plugin/verdaccio-change-password/package.json +5 -0
- package/test/partials/plugin/verdaccio-fail/fail.js +3 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import buildDebug from 'debug';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/core';
|
|
5
|
+
import { aesDecryptDeprecated as aesDecrypt, parseBasicPayload } from '@verdaccio/signature';
|
|
6
|
+
import { Security } from '@verdaccio/types';
|
|
7
|
+
|
|
8
|
+
import { AuthMiddlewarePayload } from './types';
|
|
9
|
+
import {
|
|
10
|
+
convertPayloadToBase64,
|
|
11
|
+
isAESLegacy,
|
|
12
|
+
parseAuthTokenHeader,
|
|
13
|
+
verifyJWTPayload,
|
|
14
|
+
} from './utils';
|
|
15
|
+
|
|
16
|
+
const debug = buildDebug('verdaccio:auth:utils');
|
|
17
|
+
|
|
18
|
+
export function parseAESCredentials(authorizationHeader: string, secret: string) {
|
|
19
|
+
debug('parseAESCredentials');
|
|
20
|
+
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
|
21
|
+
|
|
22
|
+
// basic is deprecated and should not be enforced
|
|
23
|
+
// basic is currently being used for functional test
|
|
24
|
+
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
|
|
25
|
+
debug('legacy header basic');
|
|
26
|
+
const credentials = convertPayloadToBase64(token).toString();
|
|
27
|
+
|
|
28
|
+
return credentials;
|
|
29
|
+
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
|
30
|
+
debug('legacy header bearer');
|
|
31
|
+
const credentials = aesDecrypt(Buffer.from(token), secret);
|
|
32
|
+
|
|
33
|
+
return credentials;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getMiddlewareCredentials(
|
|
38
|
+
security: Security,
|
|
39
|
+
secretKey: string,
|
|
40
|
+
authorizationHeader: string
|
|
41
|
+
): AuthMiddlewarePayload {
|
|
42
|
+
debug('getMiddlewareCredentials');
|
|
43
|
+
// comment out for debugging purposes
|
|
44
|
+
if (isAESLegacy(security)) {
|
|
45
|
+
debug('is legacy');
|
|
46
|
+
const credentials = parseAESCredentials(authorizationHeader, secretKey);
|
|
47
|
+
if (typeof credentials !== 'string') {
|
|
48
|
+
debug('parse legacy credentials failed');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parsedCredentials = parseBasicPayload(credentials);
|
|
53
|
+
if (!parsedCredentials) {
|
|
54
|
+
debug('parse legacy basic payload credentials failed');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return parsedCredentials;
|
|
59
|
+
}
|
|
60
|
+
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
|
61
|
+
|
|
62
|
+
debug('is jwt');
|
|
63
|
+
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
|
64
|
+
return verifyJWTPayload(token, secretKey);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/signature.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import buildDebug from 'debug';
|
|
2
|
+
import _ from 'lodash';
|
|
3
|
+
|
|
4
|
+
import { TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/core';
|
|
5
|
+
import { aesDecrypt, parseBasicPayload } from '@verdaccio/signature';
|
|
6
|
+
import { Security } from '@verdaccio/types';
|
|
7
|
+
|
|
8
|
+
import { AuthMiddlewarePayload } from './types';
|
|
9
|
+
import {
|
|
10
|
+
convertPayloadToBase64,
|
|
11
|
+
isAESLegacy,
|
|
12
|
+
parseAuthTokenHeader,
|
|
13
|
+
verifyJWTPayload,
|
|
14
|
+
} from './utils';
|
|
15
|
+
|
|
16
|
+
const debug = buildDebug('verdaccio:auth:utils');
|
|
17
|
+
|
|
18
|
+
export function parseAESCredentials(authorizationHeader: string, secret: string) {
|
|
19
|
+
debug('parseAESCredentials');
|
|
20
|
+
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
|
21
|
+
|
|
22
|
+
// basic is deprecated and should not be enforced
|
|
23
|
+
// basic is currently being used for functional test
|
|
24
|
+
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
|
|
25
|
+
debug('legacy header basic');
|
|
26
|
+
const credentials = convertPayloadToBase64(token).toString();
|
|
27
|
+
|
|
28
|
+
return credentials;
|
|
29
|
+
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
|
30
|
+
debug('legacy header bearer');
|
|
31
|
+
const credentials = aesDecrypt(token, secret);
|
|
32
|
+
|
|
33
|
+
return credentials;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function getMiddlewareCredentials(
|
|
38
|
+
security: Security,
|
|
39
|
+
secretKey: string,
|
|
40
|
+
authorizationHeader: string
|
|
41
|
+
): AuthMiddlewarePayload {
|
|
42
|
+
debug('getMiddlewareCredentials');
|
|
43
|
+
// comment out for debugging purposes
|
|
44
|
+
if (isAESLegacy(security)) {
|
|
45
|
+
debug('is legacy');
|
|
46
|
+
const credentials = parseAESCredentials(authorizationHeader, secretKey);
|
|
47
|
+
if (!credentials) {
|
|
48
|
+
debug('parse legacy credentials failed');
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const parsedCredentials = parseBasicPayload(credentials);
|
|
53
|
+
if (!parsedCredentials) {
|
|
54
|
+
debug('parse legacy basic payload credentials failed');
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return parsedCredentials;
|
|
59
|
+
}
|
|
60
|
+
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
|
61
|
+
|
|
62
|
+
debug('is jwt');
|
|
63
|
+
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
|
64
|
+
return verifyJWTPayload(token, secretKey);
|
|
65
|
+
}
|
|
66
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { NextFunction, Request, Response } from 'express';
|
|
2
|
+
|
|
3
|
+
import { VerdaccioError } from '@verdaccio/core';
|
|
4
|
+
import { AuthPackageAllow, JWTSignOptions, Logger, RemoteUser } from '@verdaccio/types';
|
|
5
|
+
|
|
6
|
+
export interface AESPayload {
|
|
7
|
+
user: string;
|
|
8
|
+
password: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type BasicPayload = AESPayload | void;
|
|
12
|
+
export type AuthMiddlewarePayload = RemoteUser | BasicPayload;
|
|
13
|
+
|
|
14
|
+
export interface AuthTokenHeader {
|
|
15
|
+
scheme: string;
|
|
16
|
+
token: string;
|
|
17
|
+
}
|
|
18
|
+
export type AllowActionCallbackResponse = boolean | undefined;
|
|
19
|
+
export type AllowActionCallback = (
|
|
20
|
+
error: VerdaccioError | null,
|
|
21
|
+
allowed?: AllowActionCallbackResponse
|
|
22
|
+
) => void;
|
|
23
|
+
|
|
24
|
+
export type AllowAction = (
|
|
25
|
+
user: RemoteUser,
|
|
26
|
+
pkg: AuthPackageAllow,
|
|
27
|
+
callback: AllowActionCallback
|
|
28
|
+
) => void;
|
|
29
|
+
|
|
30
|
+
export interface TokenEncryption {
|
|
31
|
+
jwtEncrypt(user: RemoteUser, signOptions: JWTSignOptions): Promise<string>;
|
|
32
|
+
aesEncrypt(buf: string): string | void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export type ActionsAllowed = 'publish' | 'unpublish' | 'access';
|
|
36
|
+
|
|
37
|
+
// remove
|
|
38
|
+
export interface IAuthMiddleware {
|
|
39
|
+
apiJWTmiddleware(): $NextFunctionVer;
|
|
40
|
+
webUIJWTmiddleware(): $NextFunctionVer;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export type $RequestExtend = Request & { remote_user?: any; log: Logger };
|
|
44
|
+
export type $ResponseExtend = Response & { cookies?: any };
|
|
45
|
+
export type $NextFunctionVer = NextFunction & any;
|
|
46
|
+
export { NextFunction };
|
package/src/utils.ts
CHANGED
|
@@ -7,36 +7,28 @@ import {
|
|
|
7
7
|
HTTP_STATUS,
|
|
8
8
|
TOKEN_BASIC,
|
|
9
9
|
TOKEN_BEARER,
|
|
10
|
-
VerdaccioError,
|
|
11
10
|
errorUtils,
|
|
12
11
|
pluginUtils,
|
|
13
12
|
} from '@verdaccio/core';
|
|
14
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
aesDecrypt,
|
|
15
|
+
aesDecryptDeprecated,
|
|
16
|
+
parseBasicPayload,
|
|
17
|
+
verifyPayload,
|
|
18
|
+
} from '@verdaccio/signature';
|
|
15
19
|
import { AuthPackageAllow, Config, Logger, RemoteUser, Security } from '@verdaccio/types';
|
|
16
20
|
|
|
17
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
ActionsAllowed,
|
|
23
|
+
AllowAction,
|
|
24
|
+
AllowActionCallback,
|
|
25
|
+
AuthMiddlewarePayload,
|
|
26
|
+
AuthTokenHeader,
|
|
27
|
+
TokenEncryption,
|
|
28
|
+
} from './types';
|
|
18
29
|
|
|
19
30
|
const debug = buildDebug('verdaccio:auth:utils');
|
|
20
31
|
|
|
21
|
-
export type BasicPayload = AESPayload | void;
|
|
22
|
-
export type AuthMiddlewarePayload = RemoteUser | BasicPayload;
|
|
23
|
-
|
|
24
|
-
export interface AuthTokenHeader {
|
|
25
|
-
scheme: string;
|
|
26
|
-
token: string;
|
|
27
|
-
}
|
|
28
|
-
export type AllowActionCallbackResponse = boolean | undefined;
|
|
29
|
-
export type AllowActionCallback = (
|
|
30
|
-
error: VerdaccioError | null,
|
|
31
|
-
allowed?: AllowActionCallbackResponse
|
|
32
|
-
) => void;
|
|
33
|
-
|
|
34
|
-
export type AllowAction = (
|
|
35
|
-
user: RemoteUser,
|
|
36
|
-
pkg: AuthPackageAllow,
|
|
37
|
-
callback: AllowActionCallback
|
|
38
|
-
) => void;
|
|
39
|
-
|
|
40
32
|
/**
|
|
41
33
|
* Split authentication header eg: Bearer [secret_token]
|
|
42
34
|
* @param authorizationHeader auth token
|
|
@@ -48,7 +40,11 @@ export function parseAuthTokenHeader(authorizationHeader: string): AuthTokenHead
|
|
|
48
40
|
return { scheme, token };
|
|
49
41
|
}
|
|
50
42
|
|
|
51
|
-
export function parseAESCredentials(
|
|
43
|
+
export function parseAESCredentials(
|
|
44
|
+
authorizationHeader: string,
|
|
45
|
+
secret: string,
|
|
46
|
+
enhanced: boolean
|
|
47
|
+
) {
|
|
52
48
|
debug('parseAESCredentials');
|
|
53
49
|
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
|
54
50
|
|
|
@@ -61,7 +57,11 @@ export function parseAESCredentials(authorizationHeader: string, secret: string)
|
|
|
61
57
|
return credentials;
|
|
62
58
|
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
|
63
59
|
debug('legacy header bearer');
|
|
64
|
-
|
|
60
|
+
debug('legacy header enhanced?', enhanced);
|
|
61
|
+
const credentials = enhanced
|
|
62
|
+
? aesDecrypt(token.toString(), secret)
|
|
63
|
+
: // FUTURE: once deprecated legacy is removed this logic won't be longer need it
|
|
64
|
+
aesDecryptDeprecated(convertPayloadToBase64(token), secret).toString('utf-8');
|
|
65
65
|
|
|
66
66
|
return credentials;
|
|
67
67
|
}
|
|
@@ -70,13 +70,14 @@ export function parseAESCredentials(authorizationHeader: string, secret: string)
|
|
|
70
70
|
export function getMiddlewareCredentials(
|
|
71
71
|
security: Security,
|
|
72
72
|
secretKey: string,
|
|
73
|
-
authorizationHeader: string
|
|
73
|
+
authorizationHeader: string,
|
|
74
|
+
enhanced: boolean = true
|
|
74
75
|
): AuthMiddlewarePayload {
|
|
75
76
|
debug('getMiddlewareCredentials');
|
|
76
77
|
// comment out for debugging purposes
|
|
77
78
|
if (isAESLegacy(security)) {
|
|
78
79
|
debug('is legacy');
|
|
79
|
-
const credentials = parseAESCredentials(authorizationHeader, secretKey);
|
|
80
|
+
const credentials = parseAESCredentials(authorizationHeader, secretKey, enhanced);
|
|
80
81
|
if (!credentials) {
|
|
81
82
|
debug('parse legacy credentials failed');
|
|
82
83
|
return;
|
|
@@ -161,14 +162,15 @@ export function isAuthHeaderValid(authorization: string): boolean {
|
|
|
161
162
|
export function getDefaultPlugins(logger: Logger): pluginUtils.Auth<Config> {
|
|
162
163
|
return {
|
|
163
164
|
authenticate(_user: string, _password: string, cb: pluginUtils.AuthCallback): void {
|
|
165
|
+
debug('triggered default authenticate method');
|
|
164
166
|
cb(errorUtils.getForbidden(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
165
167
|
},
|
|
166
168
|
|
|
167
169
|
adduser(_user: string, _password: string, cb: pluginUtils.AuthUserCallback): void {
|
|
170
|
+
debug('triggered default adduser method');
|
|
168
171
|
return cb(errorUtils.getConflict(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
169
172
|
},
|
|
170
173
|
|
|
171
|
-
// FIXME: allow_action and allow_publish should be in the @verdaccio/types
|
|
172
174
|
// @ts-ignore
|
|
173
175
|
allow_access: allow_action('access', logger),
|
|
174
176
|
// @ts-ignore
|
|
@@ -177,8 +179,6 @@ export function getDefaultPlugins(logger: Logger): pluginUtils.Auth<Config> {
|
|
|
177
179
|
};
|
|
178
180
|
}
|
|
179
181
|
|
|
180
|
-
export type ActionsAllowed = 'publish' | 'unpublish' | 'access';
|
|
181
|
-
|
|
182
182
|
export function allow_action(action: ActionsAllowed, logger: Logger): AllowAction {
|
|
183
183
|
return function allowActionCallback(
|
|
184
184
|
user: RemoteUser,
|
|
@@ -187,8 +187,13 @@ export function allow_action(action: ActionsAllowed, logger: Logger): AllowActio
|
|
|
187
187
|
): void {
|
|
188
188
|
logger.trace({ remote: user.name }, `[auth/allow_action]: user: @{remote}`);
|
|
189
189
|
const { name, groups } = user;
|
|
190
|
+
debug('allow_action "%s": groups %s', action, groups);
|
|
190
191
|
const groupAccess = pkg[action] as string[];
|
|
191
|
-
|
|
192
|
+
debug('allow_action "%s": groupAccess %s', action, groupAccess);
|
|
193
|
+
const hasPermission = groupAccess.some((group) => {
|
|
194
|
+
return name === group || groups.includes(group);
|
|
195
|
+
});
|
|
196
|
+
debug('package "%s" has permission "%s"', name, hasPermission);
|
|
192
197
|
logger.trace(
|
|
193
198
|
{ pkgName: pkg.name, hasPermission, remote: user.name, groupAccess },
|
|
194
199
|
`[auth/allow_action]: hasPermission? @{hasPermission} for user: @{remote}, package: @{pkgName}`
|
|
@@ -218,7 +223,8 @@ export function handleSpecialUnpublish(logger: Logger): any {
|
|
|
218
223
|
return function (user: RemoteUser, pkg: AuthPackageAllow, callback: AllowActionCallback): void {
|
|
219
224
|
const action = 'unpublish';
|
|
220
225
|
// verify whether the unpublish prop has been defined
|
|
221
|
-
const isUnpublishMissing: boolean =
|
|
226
|
+
const isUnpublishMissing: boolean = !pkg[action];
|
|
227
|
+
debug('is unpublish method missing ? %s', isUnpublishMissing);
|
|
222
228
|
const hasGroups: boolean = isUnpublishMissing ? false : (pkg[action] as string[]).length > 0;
|
|
223
229
|
logger.trace(
|
|
224
230
|
{ user: user.name, name: pkg.name, hasGroups },
|