mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
chore: fastify auth layer implementation
chore: fastify auth layer implementation
This commit is contained in:
parent
a206f476b0
commit
513407a0a4
18 changed files with 232 additions and 31 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -13,6 +13,7 @@ node_modules
|
|||
### test
|
||||
test-storage*
|
||||
.verdaccio_test_env
|
||||
packages/server/fastify/debug/storage
|
||||
|
||||
# docker examples
|
||||
docker-examples/v5/reverse_proxy/nginx/relative_path/storage/*
|
||||
|
|
|
@ -7,3 +7,4 @@ export {
|
|||
export { getLocalRegistryTarballUri } from './getLocalRegistryTarballUri';
|
||||
|
||||
export { RequestOptions };
|
||||
export { getVersionFromTarball } from './utils';
|
||||
|
|
5
packages/core/tarball/src/utils.ts
Normal file
5
packages/core/tarball/src/utils.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
export function getVersionFromTarball(name: string): string | void {
|
||||
const groups = name.match(/.+-(\d.+)\.tgz/);
|
||||
|
||||
return groups !== null ? groups[1] : undefined;
|
||||
}
|
|
@ -17,11 +17,12 @@ const debug = buildDebug('verdaccio:fastify:debug');
|
|||
const configFile = path.join(__dirname, './fastify-conf.yaml');
|
||||
debug('configFile %s', configFile);
|
||||
const configParsed = parseConfigFile(configFile);
|
||||
// @ts-ignore
|
||||
setup(configParsed.log);
|
||||
logger.info(`config location ${configFile}`);
|
||||
debug('configParsed %s', configParsed);
|
||||
process.title = 'fastify-verdaccio';
|
||||
const ser = await server({ logger, config: configParsed });
|
||||
const ser = await server(configParsed);
|
||||
await ser.listen(4873);
|
||||
logger.info('fastify running on port 4873');
|
||||
} catch (err: any) {
|
||||
|
|
|
@ -3,6 +3,9 @@ import { FastifyInstance } from 'fastify';
|
|||
|
||||
import { MergeTags } from '@verdaccio/types';
|
||||
|
||||
import allow from '../plugins/allow';
|
||||
import pkgMetadata from '../plugins/pkgMetadata';
|
||||
|
||||
const debug = buildDebug('verdaccio:fastify:dist-tags');
|
||||
|
||||
interface ParamsInterface {
|
||||
|
@ -10,6 +13,9 @@ interface ParamsInterface {
|
|||
}
|
||||
|
||||
async function distTagsRoute(fastify: FastifyInstance) {
|
||||
fastify.register(pkgMetadata);
|
||||
fastify.register(allow, { type: 'access' });
|
||||
|
||||
fastify.get<{ Params: ParamsInterface }>(
|
||||
'/-/package/:packageName/dist-tags',
|
||||
async (request, reply) => {
|
||||
|
|
|
@ -3,10 +3,13 @@ import { FastifyInstance } from 'fastify';
|
|||
|
||||
import { stringUtils } from '@verdaccio/core';
|
||||
import { Storage } from '@verdaccio/store';
|
||||
import { Package, Version } from '@verdaccio/types';
|
||||
import { Manifest, Version } from '@verdaccio/types';
|
||||
|
||||
import allow from '../plugins/allow';
|
||||
import pkgMetadata from '../plugins/pkgMetadata';
|
||||
|
||||
const debug = buildDebug('verdaccio:fastify:api:sidebar');
|
||||
export type $SidebarPackage = Package & { latest: Version };
|
||||
export type $SidebarPackage = Manifest & { latest: Version };
|
||||
|
||||
interface ParamsInterface {
|
||||
name: string;
|
||||
|
@ -14,6 +17,9 @@ interface ParamsInterface {
|
|||
}
|
||||
|
||||
async function manifestRoute(fastify: FastifyInstance) {
|
||||
fastify.register(pkgMetadata);
|
||||
fastify.register(allow, { type: 'access' });
|
||||
|
||||
fastify.get<{ Params: ParamsInterface }>('/:name', async (request) => {
|
||||
const { name } = request.params;
|
||||
const storage = fastify.storage;
|
||||
|
@ -30,6 +36,8 @@ async function manifestRoute(fastify: FastifyInstance) {
|
|||
protocol: request.protocol,
|
||||
headers: request.headers as any,
|
||||
host: request.hostname,
|
||||
// @ts-ignore
|
||||
username: request?.userRemote?.name,
|
||||
},
|
||||
abbreviated,
|
||||
});
|
||||
|
@ -41,7 +49,7 @@ async function manifestRoute(fastify: FastifyInstance) {
|
|||
}
|
||||
|
||||
fastify.get<{ Params: ParamsInterface; Querystring: QueryInterface }>(
|
||||
'/:packageName/:version',
|
||||
'/:name/:version',
|
||||
async (request) => {
|
||||
const { name, version } = request.params;
|
||||
const storage = fastify.storage;
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
import { logger } from '@verdaccio/logger';
|
||||
|
||||
async function pingRoute(fastify: FastifyInstance) {
|
||||
fastify.get('/-/ping', async () => {
|
||||
fastify.get('/-/ping', () => {
|
||||
logger.http('ping');
|
||||
return {};
|
||||
});
|
||||
|
|
|
@ -18,7 +18,8 @@ async function searchRoute(fastify: FastifyInstance) {
|
|||
// TODO: add validations for query, some parameters are mandatory
|
||||
// TODO: review which query fields are mandatory
|
||||
const abort = new AbortController();
|
||||
request.socket.on('aborted', () => {
|
||||
// https://nodejs.org/dist/latest-v18.x/docs/api/http.html#event-close
|
||||
request.socket.on('close', () => {
|
||||
abort.abort();
|
||||
});
|
||||
const { url, query } = request.query;
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import buildDebug from 'debug';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
|
||||
import { HEADERS, HEADER_TYPE } from '@verdaccio/core';
|
||||
|
||||
import allow from '../plugins/allow';
|
||||
import pkgMetadata from '../plugins/pkgMetadata';
|
||||
|
||||
const debug = buildDebug('verdaccio:fastify:tarball');
|
||||
|
||||
interface ParamsInterface {
|
||||
|
@ -12,6 +14,9 @@ interface ParamsInterface {
|
|||
}
|
||||
|
||||
async function tarballRoute(fastify: FastifyInstance) {
|
||||
fastify.register(pkgMetadata);
|
||||
fastify.register(allow, { type: 'access' });
|
||||
|
||||
fastify.get<{ Params: ParamsInterface }>('/:package/-/:filename', async (request, reply) => {
|
||||
const { package: pkg, filename } = request.params;
|
||||
debug('stream tarball for %s@%s', pkg, filename);
|
||||
|
@ -25,10 +30,10 @@ async function tarballRoute(fastify: FastifyInstance) {
|
|||
reply.header(HEADER_TYPE.CONTENT_LENGTH, size);
|
||||
});
|
||||
|
||||
// request.socket.on('abort', () => {
|
||||
// debug('request aborted for %o', request.url);
|
||||
// abort.abort();
|
||||
// });
|
||||
// https://nodejs.org/dist/latest-v18.x/docs/api/http.html#event-close
|
||||
request.socket.on('close', () => {
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
return stream;
|
||||
});
|
||||
|
@ -55,10 +60,10 @@ async function tarballRoute(fastify: FastifyInstance) {
|
|||
reply.header(HEADER_TYPE.CONTENT_LENGTH, size);
|
||||
});
|
||||
|
||||
// request.socket.on('abort', () => {
|
||||
// debug('request aborted for %o', request.url);
|
||||
// abort.abort();
|
||||
// });
|
||||
// https://nodejs.org/dist/latest-v18.x/docs/api/http.html#event-close
|
||||
request.socket.on('close', () => {
|
||||
abort.abort();
|
||||
});
|
||||
|
||||
reply.header(HEADERS.CONTENT_TYPE, HEADERS.OCTET_STREAM);
|
||||
return stream;
|
||||
|
|
|
@ -1,6 +1,3 @@
|
|||
/* eslint-disable no-console */
|
||||
|
||||
/* eslint-disable no-invalid-this */
|
||||
import buildDebug from 'debug';
|
||||
import { FastifyInstance } from 'fastify';
|
||||
import _ from 'lodash';
|
||||
|
@ -39,6 +36,7 @@ async function userRoute(fastify: FastifyInstance) {
|
|||
const { token } = request.params;
|
||||
const userRemote: RemoteUser = request.userRemote;
|
||||
await fastify.auth.invalidateToken(token);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('userRoute', userRemote);
|
||||
reply.code(fastify.statusCode.OK);
|
||||
return { ok: fastify.apiMessage.LOGGED_OUT };
|
||||
|
|
85
packages/server/fastify/src/plugins/allow.ts
Normal file
85
packages/server/fastify/src/plugins/allow.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
import { Auth } from '@verdaccio/auth';
|
||||
import { pluginUtils } from '@verdaccio/core';
|
||||
import { RemoteUser } from '@verdaccio/types';
|
||||
|
||||
/**
|
||||
* TODO: use @verdaccio/tarball
|
||||
* return package version from tarball name
|
||||
* @param {String} name
|
||||
* @deprecated use @verdaccio/tarball
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getVersionFromTarball(name: string): string | void {
|
||||
const groups = name.match(/.+-(\d.+)\.tgz/);
|
||||
|
||||
return groups !== null ? groups[1] : undefined;
|
||||
}
|
||||
|
||||
export default fp(
|
||||
async (fastify: FastifyInstance, opts: { type: string }) => {
|
||||
// ensure user remote is populated on every request
|
||||
fastify.addHook('preValidation', async function (request) {
|
||||
const remote: RemoteUser = request.userRemote;
|
||||
|
||||
switch (opts.type) {
|
||||
case 'access':
|
||||
return new Promise((resolve, reject) => {
|
||||
return fastify.auth.allow_access(request.pkgMetadata, remote, (err, allowed) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (allowed === true) {
|
||||
return resolve();
|
||||
}
|
||||
return reject(fastify.errorUtils.getForbidden());
|
||||
});
|
||||
});
|
||||
case 'publish':
|
||||
return new Promise((resolve, reject) => {
|
||||
return fastify.auth.allow_publish(request.pkgMetadata, remote, (err, allowed) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (allowed === true) {
|
||||
return resolve();
|
||||
}
|
||||
return reject(fastify.errorUtils.getForbidden());
|
||||
});
|
||||
});
|
||||
case 'unpublish':
|
||||
return new Promise((resolve, reject) => {
|
||||
return fastify.auth.allow_unpublish(request.pkgMetadata, remote, (err, allowed) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (allowed === true) {
|
||||
return resolve();
|
||||
}
|
||||
return reject(fastify.errorUtils.getForbidden());
|
||||
});
|
||||
});
|
||||
default:
|
||||
}
|
||||
});
|
||||
},
|
||||
{
|
||||
fastify: '>=4.x',
|
||||
}
|
||||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
auth: Auth;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
interface FastifyRequest {
|
||||
pkgMetadata: pluginUtils.AuthPluginPackage;
|
||||
// TODO: scope not caugh yet.
|
||||
pkgScope?: string;
|
||||
}
|
||||
}
|
|
@ -17,6 +17,7 @@ export default fp(
|
|||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
auth: Auth;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ export default fp(
|
|||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
configInstance: IConfig;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export default fp(
|
|||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
apiError: typeof API_ERROR;
|
||||
apiMessage: typeof API_MESSAGE;
|
||||
|
|
63
packages/server/fastify/src/plugins/pkgMetadata.ts
Normal file
63
packages/server/fastify/src/plugins/pkgMetadata.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
import { Auth } from '@verdaccio/auth';
|
||||
import { pluginUtils } from '@verdaccio/core';
|
||||
import { Manifest } from '@verdaccio/types';
|
||||
|
||||
/**
|
||||
* TODO: use @verdaccio/tarball
|
||||
* return package version from tarball name
|
||||
* @param {String} name
|
||||
* @deprecated use @verdaccio/tarball
|
||||
* @returns {String}
|
||||
*/
|
||||
export function getVersionFromTarball(name: string): string | void {
|
||||
const groups = name.match(/.+-(\d.+)\.tgz/);
|
||||
|
||||
return groups !== null ? groups[1] : undefined;
|
||||
}
|
||||
|
||||
export default fp(
|
||||
async (fastify: FastifyInstance) => {
|
||||
fastify.addHook<{
|
||||
Params: { scope: string; name: string; filename?: string; version?: string; tag?: string };
|
||||
Body: Manifest;
|
||||
}>('onRequest', function (request, _reply, done) {
|
||||
// TODO: scope is not implemented yet
|
||||
const { scope, name, tag, filename } = request.params;
|
||||
const packageName = typeof scope === 'string' ? `@${scope}/${name}` : name;
|
||||
// version could be a param initially
|
||||
let packageVersion: string | undefined = request.params.version;
|
||||
// when request is tarball request version comes with the tarball
|
||||
// eg: http://localhost:4873/@angular/cli/-/cli-8.3.5.tgz
|
||||
if (filename && typeof packageVersion !== 'string') {
|
||||
packageVersion = getVersionFromTarball(filename) || undefined;
|
||||
}
|
||||
const pkgMetadata: pluginUtils.AuthPluginPackage = {
|
||||
packageName,
|
||||
packageVersion,
|
||||
tag,
|
||||
};
|
||||
|
||||
request.pkgMetadata = pkgMetadata;
|
||||
|
||||
done();
|
||||
});
|
||||
},
|
||||
{
|
||||
fastify: '>=4.x',
|
||||
}
|
||||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
auth: Auth;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
interface FastifyRequest {
|
||||
pkgMetadata: pluginUtils.AuthPluginPackage;
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ export default fp(
|
|||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
storage: Storage;
|
||||
}
|
||||
|
|
27
packages/server/fastify/src/plugins/userRemoteVerify.ts
Normal file
27
packages/server/fastify/src/plugins/userRemoteVerify.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
import { FastifyInstance } from 'fastify';
|
||||
import fp from 'fastify-plugin';
|
||||
|
||||
import { Auth } from '@verdaccio/auth';
|
||||
import { createAnonymousRemoteUser } from '@verdaccio/config';
|
||||
import { Config as IConfig } from '@verdaccio/types';
|
||||
|
||||
export default fp(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async function (fastify: FastifyInstance, _opts: { config: IConfig; filters?: unknown }) {
|
||||
// ensure user remote is populated on every request
|
||||
fastify.addHook('onRequest', (request, _reply, done) => {
|
||||
request.userRemote = createAnonymousRemoteUser();
|
||||
done();
|
||||
});
|
||||
},
|
||||
{
|
||||
fastify: '>=4.x',
|
||||
}
|
||||
);
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyInstance {
|
||||
auth: Auth;
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import buildDebug from 'debug';
|
||||
import fastify from 'fastify';
|
||||
|
||||
import { Config as AppConfig, createAnonymousRemoteUser } from '@verdaccio/config';
|
||||
import { Config as AppConfig } from '@verdaccio/config';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import { ConfigYaml, Config as IConfig, RemoteUser } from '@verdaccio/types';
|
||||
|
||||
|
@ -16,6 +16,7 @@ import authPlugin from './plugins/auth';
|
|||
import configPlugin from './plugins/config';
|
||||
import coreUtils from './plugins/coreUtils';
|
||||
import storagePlugin from './plugins/storage';
|
||||
import userRemoteVerify from './plugins/userRemoteVerify';
|
||||
import login from './routes/web/api/login';
|
||||
import readme from './routes/web/api/readme';
|
||||
import sidebar from './routes/web/api/sidebar';
|
||||
|
@ -32,15 +33,11 @@ async function startServer(config: ConfigYaml): Promise<any> {
|
|||
debug('start fastify server');
|
||||
// TODO: custom logger type and logger accepted by fastify does not match
|
||||
const fastifyInstance = fastify({ logger: logger as any });
|
||||
fastifyInstance.addHook('onRequest', (request, reply, done) => {
|
||||
request.userRemote = createAnonymousRemoteUser();
|
||||
done();
|
||||
});
|
||||
fastifyInstance.register(coreUtils);
|
||||
fastifyInstance.register(configPlugin, { config });
|
||||
fastifyInstance.register(storagePlugin, { config: configInstance });
|
||||
fastifyInstance.register(authPlugin, { config: configInstance });
|
||||
|
||||
fastifyInstance.register(userRemoteVerify);
|
||||
// api
|
||||
fastifyInstance.register((instance, opts, done) => {
|
||||
instance.register(ping);
|
||||
|
@ -57,17 +54,19 @@ async function startServer(config: ConfigYaml): Promise<any> {
|
|||
done();
|
||||
});
|
||||
|
||||
// web
|
||||
fastifyInstance.register((instance, opts, done) => {
|
||||
instance.register(ping, { prefix: '/web' });
|
||||
done();
|
||||
// debugging purpose
|
||||
fastifyInstance.addHook('onRoute', (routeOptions) => {
|
||||
debug('route: prefix: %s url: %s', routeOptions.prefix, routeOptions.routePath);
|
||||
});
|
||||
|
||||
return fastifyInstance;
|
||||
}
|
||||
|
||||
declare module 'fastify' {
|
||||
// @ts-ignore
|
||||
interface FastifyRequest {
|
||||
userRemote: RemoteUser;
|
||||
authenticationHeader?: string;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue