vona-module-a-user 5.0.49 → 5.1.2
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/LICENSE +0 -0
- package/dist/.metadata/index.d.ts +17 -1
- package/dist/.metadata/index.d.ts.map +1 -0
- package/dist/.metadata/locales.d.ts +11 -0
- package/dist/.metadata/locales.d.ts.map +1 -0
- package/dist/.metadata/this.d.ts +1 -0
- package/dist/.metadata/this.d.ts.map +1 -0
- package/dist/bean/bean.passport.d.ts +5 -2
- package/dist/bean/bean.passport.d.ts.map +1 -0
- package/dist/bean/bean.role.d.ts +3 -1
- package/dist/bean/bean.role.d.ts.map +1 -0
- package/dist/bean/bean.user.d.ts +4 -3
- package/dist/bean/bean.user.d.ts.map +1 -0
- package/dist/bean/cacheRedis.authToken.d.ts +1 -0
- package/dist/bean/cacheRedis.authToken.d.ts.map +1 -0
- package/dist/bean/event.activate.d.ts +2 -1
- package/dist/bean/event.activate.d.ts.map +1 -0
- package/dist/bean/event.createAnonymous.d.ts +2 -1
- package/dist/bean/event.createAnonymous.d.ts.map +1 -0
- package/dist/bean/event.register.d.ts +2 -1
- package/dist/bean/event.register.d.ts.map +1 -0
- package/dist/bean/event.signin.d.ts +2 -1
- package/dist/bean/event.signin.d.ts.map +1 -0
- package/dist/bean/event.signout.d.ts +2 -1
- package/dist/bean/event.signout.d.ts.map +1 -0
- package/dist/bean/guard.passport.d.ts +1 -0
- package/dist/bean/guard.passport.d.ts.map +1 -0
- package/dist/bean/guard.roleName.d.ts +2 -1
- package/dist/bean/guard.roleName.d.ts.map +1 -0
- package/dist/bean/guard.userName.d.ts +2 -1
- package/dist/bean/guard.userName.d.ts.map +1 -0
- package/dist/bean/meta.printTip.d.ts +1 -0
- package/dist/bean/meta.printTip.d.ts.map +1 -0
- package/dist/bean/meta.runtime.d.ts +1 -0
- package/dist/bean/meta.runtime.d.ts.map +1 -0
- package/dist/config/config.d.ts +1 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/errors.d.ts +4 -0
- package/dist/config/errors.d.ts.map +1 -0
- package/dist/config/locale/en-us.d.ts +5 -0
- package/dist/config/locale/en-us.d.ts.map +1 -0
- package/dist/config/locale/zh-cn.d.ts +5 -0
- package/dist/config/locale/zh-cn.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +65 -20
- package/dist/index.js.map +1 -0
- package/dist/lib/auth.d.ts +1 -0
- package/dist/lib/auth.d.ts.map +1 -0
- package/dist/lib/index.d.ts +1 -0
- package/dist/lib/index.d.ts.map +1 -0
- package/dist/lib/passport.d.ts +1 -0
- package/dist/lib/passport.d.ts.map +1 -0
- package/dist/lib/user.d.ts +1 -0
- package/dist/lib/user.d.ts.map +1 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.d.ts.map +1 -0
- package/dist/service/authTokenAdapter.d.ts +2 -1
- package/dist/service/authTokenAdapter.d.ts.map +1 -0
- package/dist/service/redisToken.d.ts +1 -0
- package/dist/service/redisToken.d.ts.map +1 -0
- package/dist/types/auth.d.ts +1 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/authProfile.d.ts +1 -0
- package/dist/types/authProfile.d.ts.map +1 -0
- package/dist/types/authToken.d.ts +1 -0
- package/dist/types/authToken.d.ts.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/passport.d.ts +1 -0
- package/dist/types/passport.d.ts.map +1 -0
- package/dist/types/role.d.ts +2 -0
- package/dist/types/role.d.ts.map +1 -0
- package/dist/types/user.d.ts +1 -0
- package/dist/types/user.d.ts.map +1 -0
- package/package.json +21 -16
- package/src/.metadata/index.ts +382 -0
- package/src/.metadata/locales.ts +13 -0
- package/src/.metadata/this.ts +2 -0
- package/src/bean/bean.passport.ts +249 -0
- package/src/bean/bean.role.ts +39 -0
- package/src/bean/bean.user.ts +88 -0
- package/src/bean/cacheRedis.authToken.ts +10 -0
- package/src/bean/event.activate.ts +10 -0
- package/src/bean/event.createAnonymous.ts +10 -0
- package/src/bean/event.register.ts +14 -0
- package/src/bean/event.signin.ts +10 -0
- package/src/bean/event.signout.ts +10 -0
- package/src/bean/guard.passport.ts +53 -0
- package/src/bean/guard.roleName.ts +35 -0
- package/src/bean/guard.userName.ts +35 -0
- package/src/bean/meta.printTip.ts +18 -0
- package/src/bean/meta.runtime.ts +19 -0
- package/src/config/config.ts +28 -0
- package/src/config/errors.ts +3 -0
- package/src/config/locale/en-us.ts +3 -0
- package/src/config/locale/zh-cn.ts +3 -0
- package/src/index.ts +4 -0
- package/src/lib/auth.ts +7 -0
- package/src/lib/index.ts +3 -0
- package/src/lib/passport.ts +41 -0
- package/src/lib/user.ts +7 -0
- package/src/main.ts +25 -0
- package/src/service/authTokenAdapter.ts +42 -0
- package/src/service/redisToken.ts +68 -0
- package/src/types/auth.ts +26 -0
- package/src/types/authProfile.ts +23 -0
- package/src/types/authToken.ts +12 -0
- package/src/types/index.ts +6 -0
- package/src/types/passport.ts +29 -0
- package/src/types/role.ts +19 -0
- package/src/types/user.ts +35 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import type { IJwtClientRecord, IJwtSignOptions, IJwtToken, IJwtVerifyOptions, IPayloadData } from 'vona-module-a-jwt';
|
|
2
|
+
|
|
3
|
+
import { catchError, isNil } from '@cabloy/utils';
|
|
4
|
+
import { BeanBase, beanFullNameFromOnionName } from 'vona';
|
|
5
|
+
import { Bean } from 'vona-module-a-bean';
|
|
6
|
+
|
|
7
|
+
import type { IAuth, IAuthIdRecord, ISigninOptions } from '../types/auth.ts';
|
|
8
|
+
import type { IAuthTokenAdapter } from '../types/authToken.ts';
|
|
9
|
+
import type { IPassport, IPassportAdapter } from '../types/passport.ts';
|
|
10
|
+
import type { IRole, IRoleNameRecord } from '../types/role.ts';
|
|
11
|
+
import type { IUser, IUserNameRecord } from '../types/user.ts';
|
|
12
|
+
|
|
13
|
+
import { $getAuthIdSystem } from '../lib/auth.ts';
|
|
14
|
+
|
|
15
|
+
@Bean()
|
|
16
|
+
export class BeanPassport extends BeanBase {
|
|
17
|
+
private _authTokenAdapter: IAuthTokenAdapter;
|
|
18
|
+
private _passportAdapter: IPassportAdapter;
|
|
19
|
+
private _mockCounter: number = 0;
|
|
20
|
+
|
|
21
|
+
private get authTokenAdapter(): IAuthTokenAdapter {
|
|
22
|
+
if (!this._authTokenAdapter) {
|
|
23
|
+
const beanFullName = beanFullNameFromOnionName(this.scope.config.adapter.authToken, 'service');
|
|
24
|
+
this._authTokenAdapter = this.bean._getBean(beanFullName) as IAuthTokenAdapter;
|
|
25
|
+
}
|
|
26
|
+
return this._authTokenAdapter;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private get passportAdapter(): IPassportAdapter {
|
|
30
|
+
if (!this._passportAdapter) {
|
|
31
|
+
const beanFullName = beanFullNameFromOnionName(this.scope.config.adapter.passport, 'service');
|
|
32
|
+
this._passportAdapter = this.bean._getBean(beanFullName) as IPassportAdapter;
|
|
33
|
+
}
|
|
34
|
+
return this._passportAdapter;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
public get isAuthenticated(): boolean {
|
|
38
|
+
const user = this.currentUser;
|
|
39
|
+
return !!user && !user.anonymous;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public get isActivated(): boolean {
|
|
43
|
+
const user = this.currentUser;
|
|
44
|
+
return !!user && !!user.activated;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
public async isAdmin(): Promise<boolean> {
|
|
48
|
+
const passport = this.current;
|
|
49
|
+
return await this.passportAdapter.isAdmin(passport);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
public async setCurrent(passport: IPassport | undefined) {
|
|
53
|
+
this.ctx.state.passport = await this.passportAdapter.setCurrent(passport);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
public get current(): IPassport | undefined {
|
|
57
|
+
return this.ctx.state.passport;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
public get currentUser(): IUser | undefined {
|
|
61
|
+
return this.ctx.state.passport?.user;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public get currentAuth(): IAuth | undefined {
|
|
65
|
+
return this.ctx.state.passport?.auth;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public get currentRoles(): IRole[] | undefined {
|
|
69
|
+
return this.ctx.state.passport?.roles;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
public async signin(passport: IPassport, options?: ISigninOptions): Promise<IJwtToken> {
|
|
73
|
+
if (isNil(this.ctx.instanceName)) throw new Error('should specify instance');
|
|
74
|
+
// current
|
|
75
|
+
await this.setCurrent(passport);
|
|
76
|
+
// event
|
|
77
|
+
await this.scope.event.signin.emit(passport);
|
|
78
|
+
// serialize: payloadData for client certificate
|
|
79
|
+
const payloadData = await this._passportSerialize(passport, options);
|
|
80
|
+
// jwt token
|
|
81
|
+
return await this.bean.jwt.create(payloadData, { dev: passport.auth?.id.toString() === '-1' });
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
public async signout(): Promise<void> {
|
|
85
|
+
// current
|
|
86
|
+
const passport = this.current;
|
|
87
|
+
if (!passport) return;
|
|
88
|
+
// removeAuthToken
|
|
89
|
+
const payloadData = await this.passportAdapter.serialize(passport);
|
|
90
|
+
await this.authTokenAdapter.remove(payloadData);
|
|
91
|
+
// event
|
|
92
|
+
await this.scope.event.signout.emit(passport);
|
|
93
|
+
// ok
|
|
94
|
+
await this.setCurrent(undefined);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
public async signinSystem<K extends keyof IAuthIdRecord>(
|
|
98
|
+
authName: IAuthIdRecord[K],
|
|
99
|
+
authId: K,
|
|
100
|
+
name?: string,
|
|
101
|
+
options?: ISigninOptions,
|
|
102
|
+
): Promise<IJwtToken> {
|
|
103
|
+
if (isNil(this.ctx.instanceName)) throw new Error('should specify instance');
|
|
104
|
+
const user = await this.bean.user.findOneByName(name ?? 'admin');
|
|
105
|
+
if (!user) return this.app.throw(401);
|
|
106
|
+
const auth = { id: $getAuthIdSystem(authName, authId) };
|
|
107
|
+
const roles = await this.bean.role.findAllByUserId(user.id);
|
|
108
|
+
const passport = { user, auth, roles };
|
|
109
|
+
return await this.signin(passport, options);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
public async signinMock(name?: keyof IUserNameRecord, options?: ISigninOptions): Promise<IJwtToken> {
|
|
113
|
+
return await this.signinSystem('mock', (-10000 - ++this._mockCounter) as any, name, options);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
public async signinWithAnonymous(): Promise<void> {
|
|
117
|
+
const userAnonymous = await this.bean.user.createAnonymous();
|
|
118
|
+
const passport = { user: userAnonymous, auth: undefined };
|
|
119
|
+
await this.setCurrent(passport);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
public async kickOut(user?: IUser) {
|
|
123
|
+
if (!user) return;
|
|
124
|
+
await this.authTokenAdapter.removeAll(user);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
public async checkAuthToken(accessToken?: string, clientName?: keyof IJwtClientRecord, options?: IJwtVerifyOptions) {
|
|
128
|
+
clientName = clientName ?? 'access';
|
|
129
|
+
const [payloadData, err] = await catchError(() => {
|
|
130
|
+
return this.bean.jwt.get(clientName).verify(accessToken, options);
|
|
131
|
+
});
|
|
132
|
+
if (err) {
|
|
133
|
+
if (['access', 'refresh'].includes(clientName)) {
|
|
134
|
+
err.code = 401;
|
|
135
|
+
}
|
|
136
|
+
throw err;
|
|
137
|
+
}
|
|
138
|
+
if (!payloadData) return; // no jwt token
|
|
139
|
+
const verified = await this.authTokenAdapter.verify(payloadData);
|
|
140
|
+
if (!verified) return this.app.throw(401);
|
|
141
|
+
const passport = await this.passportAdapter.deserialize(payloadData);
|
|
142
|
+
if (!passport) return this.app.throw(401);
|
|
143
|
+
await this.setCurrent(passport);
|
|
144
|
+
return payloadData;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
public async refreshAuthToken(refreshToken: string) {
|
|
148
|
+
// checkAuthToken by code
|
|
149
|
+
let payloadData = await this.checkAuthToken(refreshToken, 'refresh');
|
|
150
|
+
if (!payloadData) return this.app.throw(401);
|
|
151
|
+
// refreshAuthToken
|
|
152
|
+
const configRefreshAuthToken = this.scope.config.passport.refreshAuthToken;
|
|
153
|
+
payloadData = await this._handlePayloadData(payloadData, { authToken: configRefreshAuthToken });
|
|
154
|
+
// jwt token
|
|
155
|
+
return await this.bean.jwt.create(payloadData);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// only created by accessToken
|
|
159
|
+
public async createTempAuthToken(options?: IJwtSignOptions) {
|
|
160
|
+
// current
|
|
161
|
+
const passport = this.current;
|
|
162
|
+
if (!passport) return this.app.throw(401);
|
|
163
|
+
// payloadData
|
|
164
|
+
const payloadData = await this._passportSerialize(passport, { authToken: 'nochange' });
|
|
165
|
+
// jwt token
|
|
166
|
+
return await this.bean.jwt.createTempAuthToken(payloadData, options);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
public async createOauthAuthToken(options?: IJwtSignOptions) {
|
|
170
|
+
// current
|
|
171
|
+
const passport = this.current;
|
|
172
|
+
if (!passport) return this.app.throw(401);
|
|
173
|
+
// payloadData
|
|
174
|
+
const payloadData = await this._passportSerialize(passport, { authToken: 'nochange' });
|
|
175
|
+
// jwt token
|
|
176
|
+
return await this.bean.jwt.createOauthAuthToken(payloadData, options);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
public async createOauthCode(options?: IJwtSignOptions) {
|
|
180
|
+
// current
|
|
181
|
+
const passport = this.current;
|
|
182
|
+
if (!passport) return this.app.throw(401);
|
|
183
|
+
// payloadData
|
|
184
|
+
const payloadData = await this._passportSerialize(passport, { authToken: 'nochange' });
|
|
185
|
+
// code
|
|
186
|
+
return await this.bean.jwt.createOauthCode(payloadData, options);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public async createOauthCodeFromOauthAuthToken(accessToken: string, options?: IJwtSignOptions) {
|
|
190
|
+
// payloadData
|
|
191
|
+
const payloadData = await this.bean.jwt.get('access').verify(accessToken);
|
|
192
|
+
if (!payloadData) return this.app.throw(401);
|
|
193
|
+
// code
|
|
194
|
+
return await this.bean.jwt.createOauthCode(payloadData, options);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public async createAuthTokenFromOauthCode(code: string) {
|
|
198
|
+
// checkAuthToken by code
|
|
199
|
+
const payloadData = await this.checkAuthToken(code, 'code');
|
|
200
|
+
if (!payloadData) return this.app.throw(401);
|
|
201
|
+
// jwt token
|
|
202
|
+
return await this.bean.jwt.create(payloadData);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
public checkRoleName(name: keyof IRoleNameRecord | (keyof IRoleNameRecord)[]) {
|
|
206
|
+
const user = this.bean.passport.currentUser;
|
|
207
|
+
if (!user || user.anonymous) return false;
|
|
208
|
+
const roles = this.bean.passport.currentRoles;
|
|
209
|
+
if (!roles) return false;
|
|
210
|
+
const roleNames = roles?.map(item => item.name as keyof IRoleNameRecord);
|
|
211
|
+
const optionsName = Array.isArray(name) ? name : [name];
|
|
212
|
+
if (!roleNames.some(roleName => optionsName.includes(roleName))) return false;
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
public checkUserName(name: keyof IUserNameRecord | (keyof IUserNameRecord)[]) {
|
|
217
|
+
const user = this.bean.passport.currentUser;
|
|
218
|
+
if (!user || user.anonymous) return false;
|
|
219
|
+
const userName = user.name as keyof IUserNameRecord;
|
|
220
|
+
const optionsName = Array.isArray(name) ? name : [name];
|
|
221
|
+
if (!optionsName.includes(userName)) return false;
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private async _passportSerialize(passport: IPassport, options?: ISigninOptions) {
|
|
226
|
+
// serialize
|
|
227
|
+
const payloadData = await this.passportAdapter.serialize(passport);
|
|
228
|
+
return await this._handlePayloadData(payloadData, options);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
private async _handlePayloadData(payloadData: IPayloadData, options?: ISigninOptions) {
|
|
232
|
+
// auth token
|
|
233
|
+
const authToken = options?.authToken ?? 'refresh';
|
|
234
|
+
if (authToken === 'recreate') {
|
|
235
|
+
return await this.authTokenAdapter.create(payloadData);
|
|
236
|
+
} else {
|
|
237
|
+
const payloadData2 = await this.authTokenAdapter.retrieve(payloadData);
|
|
238
|
+
if (!payloadData2) {
|
|
239
|
+
return await this.authTokenAdapter.create(payloadData);
|
|
240
|
+
}
|
|
241
|
+
if (authToken === 'refresh') {
|
|
242
|
+
await this.authTokenAdapter.refresh(payloadData2);
|
|
243
|
+
} else if (authToken === 'nochange') {
|
|
244
|
+
// do nothing
|
|
245
|
+
}
|
|
246
|
+
return payloadData2;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
|
|
3
|
+
import { BeanBase, beanFullNameFromOnionName } from 'vona';
|
|
4
|
+
import { Bean } from 'vona-module-a-bean';
|
|
5
|
+
|
|
6
|
+
import type { IRole, IRoleAdapter } from '../types/role.ts';
|
|
7
|
+
|
|
8
|
+
@Bean()
|
|
9
|
+
export class BeanRole extends BeanBase {
|
|
10
|
+
private _roleAdapter: IRoleAdapter;
|
|
11
|
+
|
|
12
|
+
private get roleAdapter(): IRoleAdapter {
|
|
13
|
+
if (!this._roleAdapter) {
|
|
14
|
+
const beanFullName = beanFullNameFromOnionName(this.scope.config.adapter.role, 'service');
|
|
15
|
+
this._roleAdapter = this.bean._getBean(beanFullName) as IRoleAdapter;
|
|
16
|
+
}
|
|
17
|
+
return this._roleAdapter;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
findOneByName(name: string): Promise<IRole | undefined> {
|
|
21
|
+
return this.roleAdapter.findOneByName(name);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
findOneById(id: TableIdentity): Promise<IRole | undefined> {
|
|
25
|
+
return this.roleAdapter.findOne({ id });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
findOne(role: Partial<IRole>): Promise<IRole | undefined> {
|
|
29
|
+
return this.roleAdapter.findOne(role);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
findAllByUserId(userId: TableIdentity): Promise<IRole[] | undefined> {
|
|
33
|
+
return this.roleAdapter.findAllByUserId(userId);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
addUserId(id: TableIdentity, userId: TableIdentity): Promise<TableIdentity> {
|
|
37
|
+
return this.roleAdapter.addUserId(id, userId);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import type { TableIdentity } from 'table-identity';
|
|
2
|
+
|
|
3
|
+
import { BeanBase, beanFullNameFromOnionName } from 'vona';
|
|
4
|
+
import { Bean } from 'vona-module-a-bean';
|
|
5
|
+
|
|
6
|
+
import type { IAuthUserProfile } from '../types/authProfile.ts';
|
|
7
|
+
import type { IUser, IUserAdapter } from '../types/user.ts';
|
|
8
|
+
|
|
9
|
+
@Bean()
|
|
10
|
+
export class BeanUser extends BeanBase {
|
|
11
|
+
private _userAdapter: IUserAdapter;
|
|
12
|
+
|
|
13
|
+
private get userAdapter(): IUserAdapter {
|
|
14
|
+
if (!this._userAdapter) {
|
|
15
|
+
const beanFullName = beanFullNameFromOnionName(this.scope.config.adapter.user, 'service');
|
|
16
|
+
this._userAdapter = this.bean._getBean(beanFullName) as IUserAdapter;
|
|
17
|
+
}
|
|
18
|
+
return this._userAdapter;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async activate(user: IUser) {
|
|
22
|
+
await this.scope.event.activate.emit(user, async user => {
|
|
23
|
+
await this.userAdapter.setActivated(user.id, true);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async register(user: Partial<IUser>, confirmed?: boolean, randomUsername?: boolean): Promise<IUser> {
|
|
28
|
+
// config.user.autoActivate > confirmed
|
|
29
|
+
const autoActivate = this.scope.config.user.autoActivate ? true : confirmed;
|
|
30
|
+
const data = { user, confirmed, autoActivate };
|
|
31
|
+
return (await this.scope.event.register.emit(data, async data => {
|
|
32
|
+
let name = data.user.name;
|
|
33
|
+
if (!name) this.app.throw(403);
|
|
34
|
+
// check if exists
|
|
35
|
+
const userCheck = await this.userAdapter.findOneByName(name);
|
|
36
|
+
if (userCheck) {
|
|
37
|
+
if (!randomUsername) this.scope.error.UserExists.throw();
|
|
38
|
+
name = `${name}_${userCheck.id}${Date.now().toString().substring(8)}`;
|
|
39
|
+
}
|
|
40
|
+
// user
|
|
41
|
+
const userData = { ...data.user, name };
|
|
42
|
+
const userNew = await this.userAdapter.create(userData);
|
|
43
|
+
if (data.autoActivate) {
|
|
44
|
+
await this.activate(userNew);
|
|
45
|
+
}
|
|
46
|
+
return userNew;
|
|
47
|
+
})) as IUser;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async registerByProfile(profile: IAuthUserProfile, randomUsername?: boolean): Promise<IUser> {
|
|
51
|
+
const user = await this.userAdapter.userOfProfile(profile);
|
|
52
|
+
return await this.register(user, profile.confirmed, randomUsername);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async createAnonymous(): Promise<IUser> {
|
|
56
|
+
return (await this.scope.event.createAnonymous.emit(undefined, async () => {
|
|
57
|
+
return await this.userAdapter.createAnonymous();
|
|
58
|
+
})) as Promise<IUser>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async findOneByName(name: string): Promise<IUser | undefined> {
|
|
62
|
+
return this.userAdapter.findOneByName(name);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async findOneById(id: TableIdentity): Promise<IUser | undefined> {
|
|
66
|
+
return this.userAdapter.findOne({ id });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async findOne(user: Partial<IUser>): Promise<IUser | undefined> {
|
|
70
|
+
return this.userAdapter.findOne(user);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async updateById(id: TableIdentity, user: Partial<IUser>): Promise<void> {
|
|
74
|
+
return this.userAdapter.update({ ...user, id });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async update(user: Partial<IUser>): Promise<void> {
|
|
78
|
+
return this.userAdapter.update(user);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async removeById(id: TableIdentity): Promise<void> {
|
|
82
|
+
return this.userAdapter.remove({ id });
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async remove(user: Partial<IUser>): Promise<void> {
|
|
86
|
+
return this.userAdapter.remove(user);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BeanCacheRedisBase, CacheRedis } from 'vona-module-a-cache';
|
|
2
|
+
|
|
3
|
+
export type TCacheRedisAuthTokenKey = string;
|
|
4
|
+
export type TCacheRedisAuthTokenData = string;
|
|
5
|
+
|
|
6
|
+
@CacheRedis({
|
|
7
|
+
ttl: 30 * 24 * 60 * 60 * 1000,
|
|
8
|
+
disableTransactionCompensate: true,
|
|
9
|
+
})
|
|
10
|
+
export class CacheRedisAuthToken extends BeanCacheRedisBase<TCacheRedisAuthTokenKey, TCacheRedisAuthTokenData> {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
2
|
+
|
|
3
|
+
import type { IUser } from '../types/user.ts';
|
|
4
|
+
|
|
5
|
+
export type TypeEventActivateData = IUser;
|
|
6
|
+
|
|
7
|
+
export type TypeEventActivateResult = void;
|
|
8
|
+
|
|
9
|
+
@Event()
|
|
10
|
+
export class EventActivate extends BeanEventBase<TypeEventActivateData, TypeEventActivateResult> {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
2
|
+
|
|
3
|
+
import type { IUser } from '../types/user.ts';
|
|
4
|
+
|
|
5
|
+
export type TypeEventCreateAnonymousData = undefined;
|
|
6
|
+
|
|
7
|
+
export type TypeEventCreateAnonymousResult = Partial<IUser>;
|
|
8
|
+
|
|
9
|
+
@Event()
|
|
10
|
+
export class EventCreateAnonymous extends BeanEventBase<TypeEventCreateAnonymousData, TypeEventCreateAnonymousResult> {}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
2
|
+
|
|
3
|
+
import type { IUser } from '../types/user.ts';
|
|
4
|
+
|
|
5
|
+
export interface TypeEventRegisterData {
|
|
6
|
+
user: Partial<IUser>;
|
|
7
|
+
confirmed?: boolean;
|
|
8
|
+
autoActivate?: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type TypeEventRegisterResult = Partial<IUser>;
|
|
12
|
+
|
|
13
|
+
@Event()
|
|
14
|
+
export class EventRegister extends BeanEventBase<TypeEventRegisterData, TypeEventRegisterResult> {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
2
|
+
|
|
3
|
+
import type { IPassport } from '../types/passport.ts';
|
|
4
|
+
|
|
5
|
+
export type TypeEventSigninData = IPassport;
|
|
6
|
+
|
|
7
|
+
export type TypeEventSigninResult = void;
|
|
8
|
+
|
|
9
|
+
@Event()
|
|
10
|
+
export class EventSignin extends BeanEventBase<TypeEventSigninData, TypeEventSigninResult> {}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { BeanEventBase, Event } from 'vona-module-a-event';
|
|
2
|
+
|
|
3
|
+
import type { IPassport } from '../types/passport.ts';
|
|
4
|
+
|
|
5
|
+
export type TypeEventSignoutData = IPassport;
|
|
6
|
+
|
|
7
|
+
export type TypeEventSignoutResult = void;
|
|
8
|
+
|
|
9
|
+
@Event()
|
|
10
|
+
export class EventSignout extends BeanEventBase<TypeEventSignoutData, TypeEventSignoutResult> {}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorGuardOptionsGlobal, IGuardExecute } from 'vona-module-a-aspect';
|
|
3
|
+
|
|
4
|
+
import { catchError } from '@cabloy/utils';
|
|
5
|
+
import { BeanBase, Global } from 'vona';
|
|
6
|
+
import { Guard } from 'vona-module-a-aspect';
|
|
7
|
+
import { checkErrorJwtExpired } from 'vona-module-a-jwt';
|
|
8
|
+
|
|
9
|
+
export interface IGuardOptionsPassport extends IDecoratorGuardOptionsGlobal {
|
|
10
|
+
public: boolean;
|
|
11
|
+
activated?: boolean;
|
|
12
|
+
checkAuthToken: boolean; // default is true
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Guard<IGuardOptionsPassport>({ public: false, activated: true, checkAuthToken: true })
|
|
16
|
+
@Global()
|
|
17
|
+
export class GuardPassport extends BeanBase implements IGuardExecute {
|
|
18
|
+
async execute(options: IGuardOptionsPassport, next: Next): Promise<boolean> {
|
|
19
|
+
// auth token
|
|
20
|
+
if (!this.bean.passport.current) {
|
|
21
|
+
if (options.checkAuthToken) {
|
|
22
|
+
// will return undefined if no accessToken, so not check options.public
|
|
23
|
+
const [_, err] = await catchError(() => {
|
|
24
|
+
return this.bean.passport.checkAuthToken();
|
|
25
|
+
});
|
|
26
|
+
if (err && !options.public) throw err;
|
|
27
|
+
// throw error only when ErrorMessageJwtExpired
|
|
28
|
+
checkErrorJwtExpired(err, this.ctx.headers);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
// check current
|
|
32
|
+
if (!this.bean.passport.current) {
|
|
33
|
+
await this.bean.passport.signinWithAnonymous();
|
|
34
|
+
}
|
|
35
|
+
if (!options.public && !this.bean.passport.isAuthenticated) {
|
|
36
|
+
// return false;
|
|
37
|
+
// 401 for this guard, 403 for the next guards
|
|
38
|
+
return this.app.throw(401);
|
|
39
|
+
}
|
|
40
|
+
if (this.bean.passport.isAuthenticated) {
|
|
41
|
+
if (options.activated === true && !this.bean.passport.isActivated) {
|
|
42
|
+
return this.app.throw(403);
|
|
43
|
+
}
|
|
44
|
+
if (options.activated === false && this.bean.passport.isActivated) {
|
|
45
|
+
return this.app.throw(403);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// check innerAccess
|
|
49
|
+
if (this.ctx.innerAccess) return true;
|
|
50
|
+
// next
|
|
51
|
+
return next();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorGuardOptions, IGuardExecute } from 'vona-module-a-aspect';
|
|
3
|
+
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
import { Guard } from 'vona-module-a-aspect';
|
|
6
|
+
|
|
7
|
+
import type { IRoleNameRecord } from '../types/role.ts';
|
|
8
|
+
|
|
9
|
+
export interface IGuardOptionsRoleName extends IDecoratorGuardOptions {
|
|
10
|
+
name?: keyof IRoleNameRecord | (keyof IRoleNameRecord)[];
|
|
11
|
+
passWhenMatched: boolean;
|
|
12
|
+
rejectWhenDismatched: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Guard<IGuardOptionsRoleName>({
|
|
16
|
+
passWhenMatched: true,
|
|
17
|
+
rejectWhenDismatched: true,
|
|
18
|
+
})
|
|
19
|
+
export class GuardRoleName extends BeanBase implements IGuardExecute {
|
|
20
|
+
async execute(options: IGuardOptionsRoleName, next: Next): Promise<boolean> {
|
|
21
|
+
const result = await this._check(options);
|
|
22
|
+
if (!result) {
|
|
23
|
+
if (options.rejectWhenDismatched) return this.app.throw(403);
|
|
24
|
+
} else {
|
|
25
|
+
if (options.passWhenMatched) return true;
|
|
26
|
+
}
|
|
27
|
+
// next
|
|
28
|
+
return next();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async _check(options: IGuardOptionsRoleName) {
|
|
32
|
+
if (!options.name) return false;
|
|
33
|
+
return this.bean.passport.checkRoleName(options.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Next } from 'vona';
|
|
2
|
+
import type { IDecoratorGuardOptions, IGuardExecute } from 'vona-module-a-aspect';
|
|
3
|
+
|
|
4
|
+
import { BeanBase } from 'vona';
|
|
5
|
+
import { Guard } from 'vona-module-a-aspect';
|
|
6
|
+
|
|
7
|
+
import type { IUserNameRecord } from '../types/user.ts';
|
|
8
|
+
|
|
9
|
+
export interface IGuardOptionsUserName extends IDecoratorGuardOptions {
|
|
10
|
+
name?: keyof IUserNameRecord | (keyof IUserNameRecord)[];
|
|
11
|
+
passWhenMatched: boolean;
|
|
12
|
+
rejectWhenDismatched: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
@Guard<IGuardOptionsUserName>({
|
|
16
|
+
passWhenMatched: true,
|
|
17
|
+
rejectWhenDismatched: true,
|
|
18
|
+
})
|
|
19
|
+
export class GuardUserName extends BeanBase implements IGuardExecute {
|
|
20
|
+
async execute(options: IGuardOptionsUserName, next: Next): Promise<boolean> {
|
|
21
|
+
const result = await this._check(options);
|
|
22
|
+
if (!result) {
|
|
23
|
+
if (options.rejectWhenDismatched) return this.app.throw(403);
|
|
24
|
+
} else {
|
|
25
|
+
if (options.passWhenMatched) return true;
|
|
26
|
+
}
|
|
27
|
+
// next
|
|
28
|
+
return next();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
private async _check(options: IGuardOptionsUserName) {
|
|
32
|
+
if (!options.name) return false;
|
|
33
|
+
return this.bean.passport.checkUserName(options.name);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IMetaPrintTipExecute, TypeMetaPrintTipResult } from 'vona-module-a-printtip';
|
|
2
|
+
|
|
3
|
+
import { BeanBase } from 'vona';
|
|
4
|
+
import { Meta } from 'vona-module-a-meta';
|
|
5
|
+
|
|
6
|
+
@Meta()
|
|
7
|
+
export class MetaPrintTip extends BeanBase implements IMetaPrintTipExecute {
|
|
8
|
+
async execute(): Promise<TypeMetaPrintTipResult> {
|
|
9
|
+
if (!this.app.meta.isDev) return;
|
|
10
|
+
// signin
|
|
11
|
+
const jwt = await this.bean.passport.signinSystem('dev', '-1');
|
|
12
|
+
const accessToken = jwt.accessToken;
|
|
13
|
+
return {
|
|
14
|
+
title: 'access token [admin] [dev]',
|
|
15
|
+
message: `Bearer ${accessToken}`,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { IMetaRuntimeExecute } from 'vona-module-a-runtime';
|
|
2
|
+
|
|
3
|
+
import { BeanBase } from 'vona';
|
|
4
|
+
import { Meta } from 'vona-module-a-meta';
|
|
5
|
+
|
|
6
|
+
export type TypeMetaRuntimeResult = { accessToken?: string } | undefined;
|
|
7
|
+
|
|
8
|
+
@Meta()
|
|
9
|
+
export class MetaRuntime extends BeanBase implements IMetaRuntimeExecute {
|
|
10
|
+
async execute(): Promise<TypeMetaRuntimeResult> {
|
|
11
|
+
if (this.app.meta.isProd) return;
|
|
12
|
+
// signin
|
|
13
|
+
const jwt = await this.bean.passport.signinSystem('dev', '-1');
|
|
14
|
+
const accessToken = jwt.accessToken;
|
|
15
|
+
return {
|
|
16
|
+
accessToken,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { VonaApplication } from 'vona';
|
|
2
|
+
import type { IServiceRecord } from 'vona-module-a-bean';
|
|
3
|
+
|
|
4
|
+
import type { TypeAuthToken } from '../types/auth.ts';
|
|
5
|
+
|
|
6
|
+
export function config(_app: VonaApplication) {
|
|
7
|
+
return {
|
|
8
|
+
user: {
|
|
9
|
+
autoActivate: false,
|
|
10
|
+
},
|
|
11
|
+
passport: {
|
|
12
|
+
refreshAuthToken: 'recreate' as TypeAuthToken,
|
|
13
|
+
},
|
|
14
|
+
adapter: {
|
|
15
|
+
authToken: 'a-user:authTokenAdapter' as keyof IServiceRecord,
|
|
16
|
+
passport: 'home-user:passportAdapter' as keyof IServiceRecord,
|
|
17
|
+
user: 'home-user:userAdapter' as keyof IServiceRecord,
|
|
18
|
+
role: 'home-user:roleAdapter' as keyof IServiceRecord,
|
|
19
|
+
},
|
|
20
|
+
payloadData: {
|
|
21
|
+
fields: {
|
|
22
|
+
authId: 'authId',
|
|
23
|
+
userId: 'userId',
|
|
24
|
+
token: 'token',
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
package/src/index.ts
ADDED
package/src/lib/auth.ts
ADDED
package/src/lib/index.ts
ADDED