$ npm i egg-tiaozhan-auth --save
// config/plugin.ts
plugin.tiaozhanControllerExtension = {
enable: true,
package: 'egg-tiaozhan-controller-extension',
};
plugin.passport = {
enable: true,
package: 'egg-passport',
};
plugin.tiaozhanAuth = {
enable: true,
package: 'egg-tiaozhan-auth',
};
import { Controller } from 'egg';
import { Auth, LOGIN } from 'egg-tiaozhan-auth';
export default class HomeController extends Controller {
// All users can access
async index() {
return 'tiaozhan';
}
// Only logined users
@Auth(LOGIN)
async login() {
return 'tiaozhan';
}
// need read permission
@Auth('read')
async read() {
return 'tiaozhan';
}
// nedd read and write permissions
@Auth(['read', 'write'])
async readAndWrite() {
return 'tiaozhan';
}
// need read or write permission
@Auth(can => can('read') || can('write'))
readOrWrite() {
return 'tiaozhan';
}
// need read & write permissions or edit permission
@Auth(can => can(['read', 'write']) || can('edit'))
readAndWriteOrEdit() {
return 'tiaozhan';
}
}
// config/config.default.ts
import { Context } from 'egg';
config.tiaozhanAuth = {
userToPermissions: (ctx: Context, user: any) => {
return user ? user.permissions : [];
},
};
// config/config.default.ts
config.tiaozhanAuth = {
onPass: 'pass',
onMissRoute: 'throw',
onNotLogin: 'abort',
onInvalidSymbol: 'throw',
onNoPermission: 'abort',
};
After each request is sent, TiaozhanAuth will give 5 kinds of Guard Status
according to the request, namely: Pass
, NotLogin
not logged in, NoPermission
no permission, MissRoute
route invalid, InvalidSymbol
permission definition invalid.
Among them, MissRoute
and InvalidSymbol
are triggered by the following conditions:
- Trigger
MissRoute
when you have not installed or enabledegg-tiaozhan-controller-extension
. - Trigger
MissRoute
when your current route endpoint is not a controller, a function controller, or a middleware. - Trigger
InvalidSymbol
when your Auth definition is a Symbol other thanLOGIN
.
Both guard states should be removed during development. By default, the trigger throws an exception and terminates the request. For InvalidSymbol
, just don't pass a strange Symbol to Auth to avoid it. For MissRoute
, there may be some routes that cannot modify the un-controller. For this case, please configure match
or ignore
by yourself, ignoring the specific route. For details, see match and ignore
The other three guard statuses are common guard statuses, respectively representing 'should allow this request', users not logged in, should return 401
, users do not have permission to access, should return 403
. The default policies (default actions) corresponding to the three guard states are: pass, return 4001, return 403.
type GuardSimpleStrategy = 'pass' | 'log' | 'throw' | 'abort';
pass
, pass, the middleware does nothing, continue to hand the request to the next layer.log
, pass, but the middleware will output a log and record the relevant error information. (MissRoute and InvalidSymbol have error messages, others don't)throw
, throw an exception and terminate the request directly.abort
, terminate the request and return the response directly, the request is not handed to the next layer,NotLogin
will return 401 and the error messageYou are not logined!
,NoPermission
will return 403 and error message. You have no permission!`
type GuardMessageBuilder = string | ((ctx: Context, auth: AuthOptions | null) => string);
interface GuardCommonStrategy {
type: GuardSimpleStrategy;
message?: GuardMessageBuilder;
}
CommonStrategy is designed to overcome the weakness of SimpleStrategy's inability to customize messages, where type will determine the next operation of the middleware, and message will determine the message to use when throwing an exception, printing a log, or terminating an operation, for example:
// config/config.default.ts
config.tiaozhanAuth = {
onNotLogin: {
type: 'abort',
message: 'Please login first!',
},
onNoPermission: {
type: 'abort',
message: 'You have no permission to access this page!',
},
};
The message will be replaced while status still remained to 401 and 403.
Or, you can make your message yourself:
// config/config.default.ts
import { Context } from 'egg';
import { AuthOptions } from 'egg-tiaozhan-auth';
config.tiaozhanAuth = {
onNoPermission: {
type: 'abort',
message: (ctx: Context, auth: AuthOptions | null) => {
if (typeof auth === 'string') {
return 'You have no permission to <' + auth + '>';
} else if (auth instanceof Array) {
return 'You have no permission to <' + auth.join(',') + '>';
} else {
return 'You have no permission to access<' + ctx.request.path + '>';
}
},
},
};
Warning!: The message is not all used to return to the client, the operation logic is still determined by type, the specific logic is as follows
type | message | response |
---|---|---|
Pass | discard not used | to controller |
log | print log | to controller |
throw | throw error | terminate (generally empty response) |
abort | Output response | Show the message |
type GuardMessageBuilder = string | ((ctx: Context, auth: AuthOptions | null) => string);
type GuardCallback = (ctx: Context, auth: AuthOptions | null, message: string) => any;
interface GuardCallbackStrategy {
type: 'callback';
message?: GuardMessageBuilder;
callback: GuardCallback;
}
CallbackStrategy provides more powerful custom logic than CommonStrategy, which you can use to achieve a high degree of custom processing.
The definition and usage of message is the same as that of CommonStrategy. You can use a string, or a function that returns a string, or the default, which is not covered here.
Callback is a callback that accepts 3 parameters, ctx
current request context, auth
permission configuration of the current request interface, message
message generated by the current configuration, can 'synchronously or asynchronously` not return or return response body.
For example:
// config/config.default.ts
import { Context } from 'egg';
import { AuthOptions } from 'egg-tiaozhan-auth';
config.tiaozhanAuth = {
onNoPermission: {
type: 'abort',
message: (ctx: Context, auth: AuthOptions | null) => {
if (typeof auth === 'string') {
return 'You have no permission to <' + auth + '>';
} else if (auth instanceof Array) {
return 'You have no permission to <' + auth.join(',') + '>';
} else {
return 'You have no permission to access<' + ctx.request.path + '>';
}
},
},
};
config.tiaozhanAuth = {
onNoPermission: {
type: 'abort',
message: messageBuilder,
callback: (ctx: Context, auth: AuthOptions | null, message: string) => {
// custom body
ctx.body = {
status: 0,
message,
};
// !! You have to set status yourself under CallbackStrategy
ctx.status = 403;
// async is also OK
// const bar = await ctx.service.foo.get();
// return is also OK
// return {
// status: 0,
// message,
// }
}
},
};
It can also be asynchronous or use the return value. As long as the return value is not undefined or the Promise result is not undefined, it will automatically be the response body.
type GuardMiddlewareStrategy = (ctx: Context, auth: AuthOptions | null, next: () => Promise<any>) => any;
This strategy is the most scalable one, and you can almost completely replace the middleware. The incoming ctx
and next
are the two parameters accepted by the middleware. The incoming auth is the permission configuration of the current request target method.
Under this strategy, you can complete some more complicated logic, such as outputting logs at the same time and not passing the request:
// config/config.default.ts
import { Context } from 'egg';
import { AuthOptions } from 'egg-tiaozhan-auth';
config.tiaozhanAuth = {
onInvalidSymbol: (ctx: Context, auth: AuthOptions | null, next: () => Promise<any>) => {
// async is also OK
await ctx.service.error.report('InvalidSymbol', ctx.request.path);
ctx.status = 500;
ctx.body = 'We are very sorry about this, but the server is now having a problem, please contact the system administrator, we will deal with it as soon as possible';
ctx.logger.warn('InvalidSymbol at ' + ctx.request.path);
},
};
// config/config.default.ts
config.tiaozhanAuth = {
// If skip equals true, all the request will be PASS.
skip: true,
// if alwaysReloadConfig equals true, for each request, the config will reload.
alwaysReloadConfig: true,
};
In the development phase, you may want to close the plugin because of trouble. In addition to closing in the plugin, you can also set skip
to true
in the configuration, so the request will pass through the middleware but will always pass directly.
In general, the various policies for permission configuration are static, but there may be some scenarios that need to re-read the configuration (for example, in the test code). In this case, you can set alwaysReloadConfig
to true
, so that each request will be restarted. Read the configuration to generate the corresponding logic.
Please open an issue here.