Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ES module build #3375

Open
wants to merge 13 commits into
base: dove
Choose a base branch
from
2 changes: 1 addition & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ module.exports = {
"@typescript-eslint",
"prettier"
],
"ignorePatterns": ["**/lib/", "**/dist/"],
"ignorePatterns": ["**/lib/", "**/dist/", "**/esm/", "**/types/"],
"rules": {
"prettier/prettier": "error",
"@typescript-eslint/no-explicit-any": "off",
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ dist/

# TypeScript compiled files
packages/**/lib
packages/**/esm
packages/**/types
**/build/*.tgz
*.sqlite
docs/.vitepress/cache
99 changes: 48 additions & 51 deletions docs/cookbook/authentication/apiKey.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ We will start by providing the required configuration for this strategy. You sho
Note: if all you want is api key authentication, it is still necessary to register a secret, service and entity. Since no other authentication method is used, entity can be `null`.

A fully working example with just API key authentication:

```json
{
"host": "localhost",
Expand All @@ -33,7 +34,7 @@ A fully working example with just API key authentication:
"entity": null,
"authStrategies": ["apiKey"],
"apiKey": {
"allowedKeys": [ "API_KEY_1", "API_KEY_2" ],
"allowedKeys": ["API_KEY_1", "API_KEY_2"],
"header": "x-access-token"
}
}
Expand All @@ -42,38 +43,39 @@ A fully working example with just API key authentication:

Next we will be creating a [custom strategy](../../api/authentication/strategy.md) that returns the `params` that you would like to use to identify an authenticated user/request.



<LanguageBlock global-id="ts">

```ts
import { AuthenticationBaseStrategy, AuthenticationResult, AuthenticationService } from '@feathersjs/authentication';
import { NotAuthenticated } from '@feathersjs/errors';
import { ServiceAddons } from '@feathersjs/feathers';
import { Application } from './declarations';

import {
AuthenticationBaseStrategy,
AuthenticationResult,
AuthenticationService
} from '@feathersjs/authentication'
import { NotAuthenticated } from '@feathersjs/errors'
import { ServiceAddons } from '@feathersjs/feathers'
import { Application } from './declarations'

declare module './declarations' {
interface ServiceTypes {
'authentication': AuthenticationService & ServiceAddons<any>;
authentication: AuthenticationService & ServiceAddons<any>
}
}

class ApiKeyStrategy extends AuthenticationBaseStrategy {
app: Application;
app: Application

constructor(app: Application) {
super();
this.app = app;
super()
this.app = app
}

async authenticate(authentication: AuthenticationResult) {
const { token } = authentication;
const { token } = authentication

const config = this.app.get('authentication').apiKey;
const config = this.app.get('authentication').apiKey

const match = config.allowedKeys.includes(token);
if (!match) throw new NotAuthenticated('Incorrect API Key');
const match = config.allowedKeys.includes(token)
if (!match) throw new NotAuthenticated('Incorrect API Key')

return {
apiKey: true
Expand All @@ -82,12 +84,12 @@ class ApiKeyStrategy extends AuthenticationBaseStrategy {
}

export default function (app: Application) {
const authentication = new AuthenticationService(app);
const authentication = new AuthenticationService(app)

// This can have multiple .register calls if multiple strategies have been added
authentication.register('apiKey', new ApiKeyStrategy(app));
authentication.register('apiKey', new ApiKeyStrategy(app))

app.use('/authentication', authentication);
app.use('/authentication', authentication)
}
```

Expand All @@ -98,49 +100,45 @@ export default function (app: Application) {
In `src/authentication.js`:

```js
const { AuthenticationBaseStrategy, AuthenticationService } = require('@feathersjs/authentication');
const { NotAuthenticated } = require('@feathersjs/errors');
const { AuthenticationBaseStrategy, AuthenticationService } = require('@feathersjs/authentication')
const { NotAuthenticated } = require('@feathersjs/errors')

class ApiKeyStrategy extends AuthenticationBaseStrategy {
async authenticate(authentication) {
const { token } = authentication;
const { token } = authentication

const config = this.authentication.configuration[this.name];
const config = this.authentication.configuration[this.name]

const match = config.allowedKeys.includes(token);
if (!match) throw new NotAuthenticated('Incorrect API Key');
const match = config.allowedKeys.includes(token)
if (!match) throw new NotAuthenticated('Incorrect API Key')

return {
apiKey: true
}
}
}

module.exports = app => {
const authentication = new AuthenticationService(app);
module.exports = (app) => {
const authentication = new AuthenticationService(app)
// ... authentication service setup
authentication.register('apiKey', new ApiKeyStrategy());
authentication.register('apiKey', new ApiKeyStrategy())
}
```

</LanguageBlock>



Next, we create a hook called `allow-apiKey` that sets `params.authentication` if it does not exist and if `params.provider` exists (which means it is an external call) to use that `apiKey` strategy. We will also provide the capability for the apiKey to be read from the request header: (you could also read the token as a query parameter but you will have to filter it out before it's passed to Feathers calls like `get` and `find`.



<LanguageBlock global-id="ts">

```ts
import { HookContext, NextFunction } from '@feathersjs/feathers';
import { HookContext, NextFunction } from '@feathersjs/feathers'

export default () => async (context: HookContext, next: NextFunction) => {
const { params, app } = context;
const { params, app } = context

const headerField = app.get('authentication').apiKey.header;
const token = params.headers ? params.headers[headerField] : null;
const headerField = app.get('authentication').apiKey.header
const token = params.headers ? params.headers[headerField] : null

if (token && params.provider && !params.authentication) {
context.params = {
Expand All @@ -149,10 +147,10 @@ export default () => async (context: HookContext, next: NextFunction) => {
strategy: 'apiKey',
token
}
};
}
}

return next();
return next()
}
```

Expand All @@ -162,12 +160,13 @@ export default () => async (context: HookContext, next: NextFunction) => {

```js
/* eslint-disable require-atomic-updates */
module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
return async context => {
const { params, app } = context;
module.exports = function (options = {}) {
// eslint-disable-line no-unused-vars
return async (context) => {
const { params, app } = context

const headerField = app.get('authentication').apiKey.header;
const token = params.headers[headerField];
const headerField = app.get('authentication').apiKey.header
const token = params.headers[headerField]

if (token && params.provider && !params.authentication) {
context.params = {
Expand All @@ -176,22 +175,20 @@ module.exports = function (options = {}) { // eslint-disable-line no-unused-vars
strategy: 'apiKey',
token
}
};
}
}

return context;
};
};
return context
}
}
```

</LanguageBlock>



This hook should be added __before__ the [authenticate hook](../../api/authentication/hook.md) wherever API Key authentication should be allowed:
This hook should be added **before** the [authenticate hook](../../api/authentication/hook.md) wherever API Key authentication should be allowed:

```js
import { authenticate } from '@feathersjs/authentication/lib/hooks';
import { authenticate } from '@feathersjs/authentication';
import allowApiKey from './hooks/allow-api-key';

all: [ allowApiKey(), authenticate('jwt', 'apiKey') ],
Expand Down
4 changes: 1 addition & 3 deletions docs/guides/migrating.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ Now `import { HookContext } from './declarations'` can be used as the context in

### Service types



Service types now only need the actual service class type and should no longer include the `& ServiceAddons<any>`. E.g. for the messages service like this:

```ts
Expand Down Expand Up @@ -97,7 +95,7 @@ export interface MyServiceParams extends Params<MyQuery> {
You can revert to the previous behaviour by overriding he `Params` declaration:

```ts
declare module '@feathersjs/feathers/lib/declarations' {
declare module '@feathersjs/feathers' {
interface Params {
[key: string]: any
}
Expand Down
67 changes: 67 additions & 0 deletions generators/update-esm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import type { PinionContext } from '@feathershq/pinion'
import { generator, loadJSON, writeJSON } from '@feathershq/pinion'

interface Context extends PinionContext {
pkg: any
tsconfig: any
}

// A generator that rewrites the package.json and tsconfig.json files for ESM builds
export const generate = (context: Context) =>
generator(context)
.then(loadJSON('package.json', (pkg) => ({ pkg })))
.then(loadJSON('tsconfig.json', (tsconfig) => ({ tsconfig })))
.then(
writeJSON<Context>(
({ pkg }) => ({
...pkg,
module: './esm/index.js',
main: './lib/index.js',
types: './types/index.d.ts',
exports: {
'.': {
import: './esm/index.js',
require: './lib/index.js',
types: './types/index.d.ts'
}
},
files: [
'CHANGELOG.md',
'LICENSE',
'README.md',
'src/**',
'lib/**',
'types/**',
'esm/**',
'*.d.ts',
'*.js'
],
engines: {
node: '>= 18'
},
scripts: {
...pkg.scripts,
'compile:esm':
'shx rm -rf esm/ && tsc --outDir esm/ --module esnext && shx echo \'{ "type": "module" }\' > esm/package.json',
'compile:cjs':
'npx shx rm -rf types/ lib/ && tsc && shx echo \'{ "type": "commonjs" }\' > lib/package.json',
compile: 'npm run compile:cjs && npm run compile:esm && npm run pack'
}
}),
'package.json',
{ force: true }
)
)
.then(
writeJSON<Context>(
({ tsconfig }) => ({
...tsconfig,
compilerOptions: {
declarationDir: './types',
outDir: 'lib'
}
}),
'tsconfig.json',
{ force: true }
)
)
Loading
Loading