@tomei/sso 0.38.9 → 0.39.0
Sign up to get free protection for your applications and to get access to all the features.
- package/dist/src/components/api-key/api-key.d.ts +39 -0
- package/dist/src/components/api-key/api-key.js +125 -0
- package/dist/src/components/api-key/api-key.js.map +1 -0
- package/dist/src/components/api-key/api-key.repository.d.ts +6 -0
- package/dist/src/components/api-key/api-key.repository.js +26 -0
- package/dist/src/components/api-key/api-key.repository.js.map +1 -0
- package/dist/src/components/api-key/index.d.ts +3 -0
- package/dist/src/components/api-key/index.js +8 -0
- package/dist/src/components/api-key/index.js.map +1 -0
- package/dist/src/components/login-user/user.js +4 -4
- package/dist/src/components/login-user/user.js.map +1 -1
- package/dist/src/enum/api-key.enum.d.ts +5 -0
- package/dist/src/enum/api-key.enum.js +10 -0
- package/dist/src/enum/api-key.enum.js.map +1 -0
- package/dist/src/enum/index.d.ts +1 -0
- package/dist/src/enum/index.js +1 -0
- package/dist/src/enum/index.js.map +1 -1
- package/dist/src/interfaces/api-key-attr.interface.d.ts +13 -0
- package/dist/src/interfaces/api-key-attr.interface.js +3 -0
- package/dist/src/interfaces/api-key-attr.interface.js.map +1 -0
- package/dist/src/interfaces/index.d.ts +1 -0
- package/dist/src/interfaces/index.js +1 -0
- package/dist/src/interfaces/index.js.map +1 -1
- package/dist/src/models/api-key-entity.d.ts +17 -0
- package/dist/src/models/api-key-entity.js +105 -0
- package/dist/src/models/api-key-entity.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/migrations/20240528063108-create-api-key-table.js +89 -0
- package/package.json +1 -1
- package/src/components/api-key/api-key.repository.ts +15 -0
- package/src/components/api-key/api-key.ts +196 -0
- package/src/components/api-key/index.ts +4 -0
- package/src/components/login-user/user.ts +4 -4
- package/src/enum/api-key.enum.ts +5 -0
- package/src/enum/index.ts +1 -0
- package/src/interfaces/api-key-attr.interface.ts +14 -0
- package/src/interfaces/index.ts +1 -0
- package/src/models/api-key-entity.ts +87 -0
@@ -0,0 +1,89 @@
|
|
1
|
+
'use strict';
|
2
|
+
|
3
|
+
/** @type {import('sequelize-cli').Migration} */
|
4
|
+
module.exports = {
|
5
|
+
async up(queryInterface, Sequelize) {
|
6
|
+
await queryInterface.createTable(
|
7
|
+
'sso_APIKey',
|
8
|
+
{
|
9
|
+
APIKeyId: {
|
10
|
+
primaryKey: true,
|
11
|
+
type: Sequelize.INTEGER,
|
12
|
+
allowNull: false,
|
13
|
+
autoIncrement: true,
|
14
|
+
},
|
15
|
+
APIKey: {
|
16
|
+
type: Sequelize.STRING(128),
|
17
|
+
allowNull: false,
|
18
|
+
unique: true,
|
19
|
+
},
|
20
|
+
Name: {
|
21
|
+
type: Sequelize.STRING(255),
|
22
|
+
allowNull: false,
|
23
|
+
},
|
24
|
+
Description: {
|
25
|
+
type: Sequelize.TEXT,
|
26
|
+
allowNull: true,
|
27
|
+
},
|
28
|
+
Status: {
|
29
|
+
type: Sequelize.STRING(50),
|
30
|
+
defaultValue: 'Active',
|
31
|
+
allowNull: false,
|
32
|
+
},
|
33
|
+
ExpirationDate: {
|
34
|
+
allowNull: false,
|
35
|
+
type: Sequelize.DATE,
|
36
|
+
},
|
37
|
+
CreatedAt: {
|
38
|
+
allowNull: false,
|
39
|
+
defaultValue: Sequelize.literal('CURRENT_TIMESTAMP(3)'),
|
40
|
+
type: Sequelize.DATE,
|
41
|
+
},
|
42
|
+
CreatedById: {
|
43
|
+
type: Sequelize.INTEGER,
|
44
|
+
allowNull: true,
|
45
|
+
references: {
|
46
|
+
model: 'sso_User',
|
47
|
+
key: 'UserId',
|
48
|
+
},
|
49
|
+
onDelete: 'CASCADE',
|
50
|
+
onUpdate: 'CASCADE',
|
51
|
+
},
|
52
|
+
InvokedAt: {
|
53
|
+
allowNull: true,
|
54
|
+
type: Sequelize.DATE,
|
55
|
+
},
|
56
|
+
InvokedById: {
|
57
|
+
type: Sequelize.INTEGER,
|
58
|
+
allowNull: true,
|
59
|
+
references: {
|
60
|
+
model: 'sso_User',
|
61
|
+
key: 'UserId',
|
62
|
+
},
|
63
|
+
onDelete: 'CASCADE',
|
64
|
+
onUpdate: 'CASCADE',
|
65
|
+
},
|
66
|
+
},
|
67
|
+
{
|
68
|
+
uniqueKeys: {
|
69
|
+
IDX_ApiKey: {
|
70
|
+
customIndex: true,
|
71
|
+
fields: ['APIKey'],
|
72
|
+
},
|
73
|
+
IDX_CreatedById: {
|
74
|
+
customIndex: true,
|
75
|
+
fields: ['CreatedById'],
|
76
|
+
},
|
77
|
+
IDX_InvokedById: {
|
78
|
+
customIndex: true,
|
79
|
+
fields: ['InvokedById'],
|
80
|
+
},
|
81
|
+
},
|
82
|
+
},
|
83
|
+
);
|
84
|
+
},
|
85
|
+
|
86
|
+
async down(queryInterface, Sequelize) {
|
87
|
+
await queryInterface.dropTable('sso_APIKey');
|
88
|
+
},
|
89
|
+
};
|
package/package.json
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
import { RepositoryBase, IRepositoryBase } from '@tomei/general';
|
2
|
+
import APIKeyModel from 'models/api-key-entity';
|
3
|
+
|
4
|
+
export class APIKeyRepository
|
5
|
+
extends RepositoryBase<APIKeyModel>
|
6
|
+
implements IRepositoryBase<APIKeyModel>
|
7
|
+
{
|
8
|
+
constructor() {
|
9
|
+
super(APIKeyModel);
|
10
|
+
}
|
11
|
+
|
12
|
+
async findByPk(id: string, options?: any): Promise<APIKeyModel> {
|
13
|
+
return await APIKeyModel.findByPk(parseInt(id), options);
|
14
|
+
}
|
15
|
+
}
|
@@ -0,0 +1,196 @@
|
|
1
|
+
import { ClassError, ObjectBase } from '@tomei/general';
|
2
|
+
import { APIKeyStatusEnum } from '../../enum/api-key.enum';
|
3
|
+
import { APIKeyRepository } from './api-key.repository';
|
4
|
+
import { IAPIKeyAttr } from '../../interfaces/api-key-attr.interface';
|
5
|
+
import { LoginUser } from '../login-user/login-user';
|
6
|
+
import { ApplicationConfig } from '@tomei/config';
|
7
|
+
import { randomUUID, randomBytes } from 'crypto';
|
8
|
+
import { ActionEnum, Activity } from '@tomei/activity-history';
|
9
|
+
|
10
|
+
export class APIKey extends ObjectBase {
|
11
|
+
ObjectId: string;
|
12
|
+
ObjectName: string;
|
13
|
+
ObjectType: 'ApiKey';
|
14
|
+
TableName: 'sso_APIKey';
|
15
|
+
|
16
|
+
ApiKey: string;
|
17
|
+
Name: string;
|
18
|
+
Description: string;
|
19
|
+
Status: APIKeyStatusEnum;
|
20
|
+
ExperationDate: Date;
|
21
|
+
_InvokedById: number;
|
22
|
+
_InvokedAt: Date;
|
23
|
+
_CreatedAt: Date;
|
24
|
+
_CreatedById: number;
|
25
|
+
|
26
|
+
get APIKeyId(): number {
|
27
|
+
return parseInt(this.ObjectId);
|
28
|
+
}
|
29
|
+
|
30
|
+
set APIKeyId(value: number) {
|
31
|
+
this.ObjectId = value.toString();
|
32
|
+
}
|
33
|
+
|
34
|
+
get InvokedById(): number {
|
35
|
+
return this._InvokedById;
|
36
|
+
}
|
37
|
+
|
38
|
+
get InvokedAt(): Date {
|
39
|
+
return this._InvokedAt;
|
40
|
+
}
|
41
|
+
|
42
|
+
get CreatedAt(): Date {
|
43
|
+
return this._CreatedAt;
|
44
|
+
}
|
45
|
+
|
46
|
+
get CreatedById(): number {
|
47
|
+
return this._CreatedById;
|
48
|
+
}
|
49
|
+
|
50
|
+
protected static _Repo = new APIKeyRepository();
|
51
|
+
|
52
|
+
protected constructor(apiKeyAttr?: IAPIKeyAttr) {
|
53
|
+
super();
|
54
|
+
if (apiKeyAttr) {
|
55
|
+
this.APIKeyId = apiKeyAttr.APIKeyId;
|
56
|
+
this.ApiKey = apiKeyAttr.ApiKey;
|
57
|
+
this.Name = apiKeyAttr.Name;
|
58
|
+
this.Description = apiKeyAttr.Description;
|
59
|
+
this.Status = apiKeyAttr.Status;
|
60
|
+
this.ExperationDate = apiKeyAttr.ExperationDate;
|
61
|
+
this._InvokedById = apiKeyAttr.InvokedById;
|
62
|
+
this._InvokedAt = apiKeyAttr.InvokedAt;
|
63
|
+
this._CreatedAt = apiKeyAttr.CreatedAt;
|
64
|
+
this._CreatedById = apiKeyAttr.CreatedById;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
|
68
|
+
public static async init(ApiKeyId?: number, dbTransaction?: any) {
|
69
|
+
try {
|
70
|
+
if (ApiKeyId) {
|
71
|
+
const apiKeyAttr = await this._Repo.findByPk(
|
72
|
+
ApiKeyId.toString(),
|
73
|
+
dbTransaction,
|
74
|
+
);
|
75
|
+
if (apiKeyAttr) {
|
76
|
+
return new APIKey(apiKeyAttr);
|
77
|
+
} else {
|
78
|
+
throw new ClassError(
|
79
|
+
'APIKey',
|
80
|
+
'APIKeyErrMsgO1',
|
81
|
+
'APIKey not found',
|
82
|
+
'init',
|
83
|
+
);
|
84
|
+
}
|
85
|
+
}
|
86
|
+
return new APIKey();
|
87
|
+
} catch (error) {
|
88
|
+
throw error;
|
89
|
+
}
|
90
|
+
}
|
91
|
+
|
92
|
+
async generate(loginUser: LoginUser, dbTransaction?: any) {
|
93
|
+
//This method generates a new API key and saves it into the database.
|
94
|
+
try {
|
95
|
+
// Part 2: Privilege Checking
|
96
|
+
// Call loginUser.checkPrivileges() method by passing:
|
97
|
+
// SystemCode: <get_from_app_config>
|
98
|
+
// PrivilegeCode: "API_KEY_CREATE"
|
99
|
+
const systemCode =
|
100
|
+
ApplicationConfig.getComponentConfigValue('system-code');
|
101
|
+
const isPrivileged = await loginUser.checkPrivileges(
|
102
|
+
systemCode,
|
103
|
+
'API_KEY_CREATE',
|
104
|
+
);
|
105
|
+
|
106
|
+
if (!isPrivileged) {
|
107
|
+
throw new ClassError(
|
108
|
+
'APIKey',
|
109
|
+
'APIKeyErrMsgO2',
|
110
|
+
'User does not have privilege to generate API key.',
|
111
|
+
'generate',
|
112
|
+
);
|
113
|
+
}
|
114
|
+
|
115
|
+
// Part 3: Generate API Key
|
116
|
+
// Populate the following attributes:
|
117
|
+
// CreatedById: Set to loginUser.UserId.
|
118
|
+
this._CreatedById = loginUser.UserId;
|
119
|
+
// CreatedAt: Set to current date time.
|
120
|
+
this._CreatedAt = new Date();
|
121
|
+
// Generate a unique API key string:
|
122
|
+
// Use a secure random function or crypto module to generate a 64-character hexadecimal string.
|
123
|
+
// Assign the generated string to this.ApiKey.
|
124
|
+
this.ApiKey = randomBytes(64).toString('hex');
|
125
|
+
|
126
|
+
// Part 4: Save API Key to Database
|
127
|
+
// Call APIKey._Repo.create() by passing:
|
128
|
+
// loginUser
|
129
|
+
// dbTransaction
|
130
|
+
// This APIKey instance.
|
131
|
+
const EntityValueAfter: any = {
|
132
|
+
ApiKey: this.ApiKey,
|
133
|
+
Name: this.Name,
|
134
|
+
Status: this.Status,
|
135
|
+
ExpirationDate: this.ExperationDate,
|
136
|
+
CreatedAt: this.CreatedAt,
|
137
|
+
CreatedById: this.CreatedById,
|
138
|
+
InvokedAt: this.InvokedAt,
|
139
|
+
InvokedById: this.InvokedById,
|
140
|
+
};
|
141
|
+
|
142
|
+
const data = await APIKey._Repo.create(EntityValueAfter, {
|
143
|
+
transaction: dbTransaction,
|
144
|
+
});
|
145
|
+
|
146
|
+
EntityValueAfter.ApiKeyId = data.APIKeyId;
|
147
|
+
|
148
|
+
// Part 5: Record Generate API Key Activity
|
149
|
+
// Initialise EntityValueBefore variable and set to empty object.
|
150
|
+
const EntityValueBefore = {};
|
151
|
+
// Initialise EntityValueAfter variable and set to this APIKey instance.
|
152
|
+
// Instantiate new activity from Activity class, call createId() method, then set:
|
153
|
+
const activity = new Activity();
|
154
|
+
activity.ActivityId = activity.createId();
|
155
|
+
// Action: ActionEnum.Create
|
156
|
+
// Description: "Generate API key."
|
157
|
+
// EntityType: "APIKey"
|
158
|
+
// EntityId: <this.APIKeyId>
|
159
|
+
// EntityValueBefore: EntityValueBefore
|
160
|
+
// EntityValueAfter: EntityValueAfter
|
161
|
+
activity.Action = ActionEnum.CREATE;
|
162
|
+
activity.Description = 'Generate API key.';
|
163
|
+
activity.EntityType = 'APIKey';
|
164
|
+
activity.EntityId = this.ObjectId;
|
165
|
+
activity.EntityValueBefore = JSON.stringify(EntityValueBefore);
|
166
|
+
activity.EntityValueAfter = JSON.stringify(EntityValueAfter);
|
167
|
+
// Call new activity create method by passing:
|
168
|
+
// dbTransaction
|
169
|
+
// userId: loginUser.ObjectId
|
170
|
+
await activity.create(loginUser.ObjectId, dbTransaction);
|
171
|
+
|
172
|
+
// Part 6: Returns
|
173
|
+
// Translate the created APIKey entity into an object and return the following fields:
|
174
|
+
// ApiKey
|
175
|
+
// Name
|
176
|
+
// Status
|
177
|
+
// ExpirationDate
|
178
|
+
// CreatedAt
|
179
|
+
// CreatedById
|
180
|
+
// InvokedAt
|
181
|
+
// InvokedById
|
182
|
+
return {
|
183
|
+
ApiKey: this.ApiKey,
|
184
|
+
Name: this.Name,
|
185
|
+
Status: this.Status,
|
186
|
+
ExpirationDate: this.ExperationDate,
|
187
|
+
CreatedAt: this.CreatedAt,
|
188
|
+
CreatedById: this.CreatedById,
|
189
|
+
InvokedAt: this.InvokedAt,
|
190
|
+
InvokedById: this.InvokedById,
|
191
|
+
};
|
192
|
+
} catch (error) {
|
193
|
+
throw error;
|
194
|
+
}
|
195
|
+
}
|
196
|
+
}
|
@@ -32,6 +32,7 @@ import { LoginStatusEnum } from '../../enum/login-status.enum';
|
|
32
32
|
import { RedisService } from '../../redis-client/redis.service';
|
33
33
|
import { LoginUser } from './login-user';
|
34
34
|
import { SessionService } from '../../session/session.service';
|
35
|
+
import { randomUUID } from 'crypto';
|
35
36
|
|
36
37
|
export class User extends UserBase {
|
37
38
|
ObjectId: string;
|
@@ -565,16 +566,15 @@ export class User extends UserBase {
|
|
565
566
|
const userSession = await this._SessionService.retrieveUserSession(
|
566
567
|
this.ObjectId,
|
567
568
|
);
|
568
|
-
|
569
|
+
const systemLogin = userSession.systemLogins.find(
|
569
570
|
(system) => system.code === systemCode,
|
570
571
|
);
|
571
572
|
|
572
573
|
// generate new session id
|
573
|
-
const
|
574
|
-
const sessionId = randomUUID();
|
574
|
+
const sessionId: string = randomUUID();
|
575
575
|
|
576
576
|
if (systemLogin) {
|
577
|
-
systemLogin
|
577
|
+
systemLogin.sessionId = sessionId;
|
578
578
|
userSession.systemLogins.map((system) =>
|
579
579
|
system.code === systemCode ? systemLogin : system,
|
580
580
|
);
|
package/src/enum/index.ts
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
import { APIKeyStatusEnum } from '../enum/api-key.enum';
|
2
|
+
|
3
|
+
export interface IAPIKeyAttr {
|
4
|
+
APIKeyId: number;
|
5
|
+
ApiKey: string;
|
6
|
+
Name: string;
|
7
|
+
Description: string;
|
8
|
+
Status: APIKeyStatusEnum;
|
9
|
+
ExperationDate: Date;
|
10
|
+
CreatedById: number;
|
11
|
+
InvokedById: number;
|
12
|
+
CreatedAt: Date;
|
13
|
+
InvokedAt: Date;
|
14
|
+
}
|
package/src/interfaces/index.ts
CHANGED
@@ -0,0 +1,87 @@
|
|
1
|
+
import {
|
2
|
+
BelongsTo,
|
3
|
+
Column,
|
4
|
+
CreatedAt,
|
5
|
+
DataType,
|
6
|
+
ForeignKey,
|
7
|
+
Index,
|
8
|
+
Model,
|
9
|
+
Table,
|
10
|
+
} from 'sequelize-typescript';
|
11
|
+
import User from './user.entity';
|
12
|
+
import { APIKeyStatusEnum } from '../enum/api-key.enum';
|
13
|
+
|
14
|
+
@Table({
|
15
|
+
tableName: 'sso_ApiKey',
|
16
|
+
timestamps: true,
|
17
|
+
createdAt: 'CreatedAt',
|
18
|
+
updatedAt: false,
|
19
|
+
})
|
20
|
+
export default class APIKeyModel extends Model {
|
21
|
+
@Column({
|
22
|
+
primaryKey: true,
|
23
|
+
type: DataType.INTEGER,
|
24
|
+
allowNull: false,
|
25
|
+
autoIncrement: true,
|
26
|
+
})
|
27
|
+
APIKeyId: number;
|
28
|
+
|
29
|
+
@Column({
|
30
|
+
type: DataType.STRING(128),
|
31
|
+
})
|
32
|
+
@Index('IDX_ApiKey')
|
33
|
+
ApiKey: string;
|
34
|
+
|
35
|
+
@Column({
|
36
|
+
type: DataType.STRING(255),
|
37
|
+
allowNull: false,
|
38
|
+
})
|
39
|
+
Name: string;
|
40
|
+
|
41
|
+
@Column({
|
42
|
+
type: DataType.TEXT,
|
43
|
+
allowNull: true,
|
44
|
+
})
|
45
|
+
Description: string;
|
46
|
+
|
47
|
+
@Column({
|
48
|
+
type: DataType.STRING(50),
|
49
|
+
allowNull: false,
|
50
|
+
})
|
51
|
+
Status: APIKeyStatusEnum;
|
52
|
+
|
53
|
+
@Column({
|
54
|
+
type: DataType.DATE,
|
55
|
+
allowNull: true,
|
56
|
+
})
|
57
|
+
ExperationDate: Date;
|
58
|
+
|
59
|
+
@ForeignKey(() => User)
|
60
|
+
@Column({
|
61
|
+
type: DataType.INTEGER,
|
62
|
+
})
|
63
|
+
@Index('IDX_CreatedById')
|
64
|
+
CreatedById: number;
|
65
|
+
|
66
|
+
@ForeignKey(() => User)
|
67
|
+
@Column({
|
68
|
+
type: DataType.INTEGER,
|
69
|
+
})
|
70
|
+
@Index('IDX_InvokedById')
|
71
|
+
InvokedById: number;
|
72
|
+
|
73
|
+
@CreatedAt
|
74
|
+
CreatedAt: Date;
|
75
|
+
|
76
|
+
@Column({
|
77
|
+
type: DataType.DATE,
|
78
|
+
allowNull: true,
|
79
|
+
})
|
80
|
+
InvokedAt: Date;
|
81
|
+
|
82
|
+
@BelongsTo(() => User, 'CreatedById')
|
83
|
+
CreatedByUser: User;
|
84
|
+
|
85
|
+
@BelongsTo(() => User, 'RevokedById')
|
86
|
+
UpdatedByUser: User;
|
87
|
+
}
|