@thesinding/authentication-api-key
Version:
API Key authentication strategy for @feathers/authentication
107 lines (95 loc) • 2.75 kB
text/typescript
import { Params } from "@feathersjs/feathers";
import { InvalidAPIError } from "./InvalidAPIKeyError";
import { NotAuthenticated } from "@feathersjs/errors";
import { IncomingMessage, ServerResponse } from "http";
import {
AuthenticationBaseStrategy,
AuthenticationResult,
} from "@feathersjs/authentication";
interface Configuration {
entity: string;
service: string;
key: string;
revokedField: string;
headerField: string;
}
export class ApiKeyStrategy extends AuthenticationBaseStrategy {
private serviceBased: boolean = false;
constructor() {
super();
}
get configuration(): Configuration {
const config = super.configuration || {};
return { entity: "api-key", ...config };
}
verifyConfiguration() {
this.serviceBased = ["service", "entity"].every(
(prop) => prop in this.configuration
);
if (!this.serviceBased) {
if (!("key" in this.configuration)) {
throw new Error(
`A static key is missing, when strategy '${this.name}', is not service based`
);
}
}
["headerField"].forEach((prop) => {
if (prop in this.configuration) return;
throw new Error(`'${prop}' is missing from configuration`);
});
}
async findEntity(apiKey: string, params: Params) {
const { entity } = this.configuration;
try {
const result = await this.entityService.find({
query: { [entity]: apiKey, $limit: 1 },
paginate: false,
});
if (result.length === 0) {
throw new InvalidAPIError();
}
return result[0];
} catch (error) {
throw new InvalidAPIError();
}
}
async authenticate(authRequest: AuthenticationResult, params: Params) {
const { key, entity, revokedField, headerField } = this.configuration;
const apiKey = authRequest[entity];
const response = {
authentication: {
strategy: this.name,
[entity]: apiKey,
},
headers: {
...params.headers,
[headerField]: apiKey,
},
apiKey: true,
[entity]: {},
};
if (!this.serviceBased) {
if (key !== apiKey) throw new InvalidAPIError();
return response;
}
const apiKeyData = await this.findEntity(apiKey, params);
if (revokedField in apiKeyData) {
if (apiKeyData[revokedField]) {
throw new NotAuthenticated("API Key has been revoked");
}
}
response[entity] = apiKeyData;
return response;
}
async parse(req: IncomingMessage, res: ServerResponse) {
const { headerField, entity } = this.configuration;
const apiKey = req.headers[headerField];
if (apiKey) {
return {
strategy: this.name,
[entity]: apiKey,
};
}
return null;
}
}