@aranzatech/aranza-auth 0.1.0
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/CHANGELOG.md +40 -0
- package/README.md +468 -0
- package/dist/auth-repository.interface-BMlJc-98.d.cts +90 -0
- package/dist/auth-repository.interface-BMlJc-98.d.ts +90 -0
- package/dist/chunk-DKYNHXY2.js +36 -0
- package/dist/chunk-DKYNHXY2.js.map +1 -0
- package/dist/index.cjs +681 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +127 -0
- package/dist/index.d.ts +127 -0
- package/dist/index.js +618 -0
- package/dist/index.js.map +1 -0
- package/dist/mongo/index.cjs +256 -0
- package/dist/mongo/index.cjs.map +1 -0
- package/dist/mongo/index.d.cts +65 -0
- package/dist/mongo/index.d.ts +65 -0
- package/dist/mongo/index.js +226 -0
- package/dist/mongo/index.js.map +1 -0
- package/package.json +102 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { __decorateClass, resolveRegisterIdentifier, normalizeIdentifier, AUTH_REPOSITORY, AUTH_MODULE_OPTIONS } from '../chunk-DKYNHXY2.js';
|
|
2
|
+
import { Injectable, Module } from '@nestjs/common';
|
|
3
|
+
import { Prop, Schema, SchemaFactory, MongooseModule, getModelToken } from '@nestjs/mongoose';
|
|
4
|
+
import { Types } from 'mongoose';
|
|
5
|
+
|
|
6
|
+
function toAccount(doc) {
|
|
7
|
+
return {
|
|
8
|
+
id: doc._id.toString(),
|
|
9
|
+
...doc.email != null ? { email: doc.email } : {},
|
|
10
|
+
...doc.username != null ? { username: doc.username } : {},
|
|
11
|
+
emailVerified: doc.emailVerified,
|
|
12
|
+
disabled: doc.disabled,
|
|
13
|
+
createdAt: doc.createdAt,
|
|
14
|
+
updatedAt: doc.updatedAt
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function toAccountWithSecrets(doc) {
|
|
18
|
+
return {
|
|
19
|
+
...toAccount(doc),
|
|
20
|
+
passwordHash: doc.passwordHash,
|
|
21
|
+
refreshTokenHash: doc.refreshTokenHash ?? null
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
var MongoAuthRepository = class {
|
|
25
|
+
constructor(authModel, identifierField = "email") {
|
|
26
|
+
this.authModel = authModel;
|
|
27
|
+
this.identifierField = identifierField;
|
|
28
|
+
}
|
|
29
|
+
async create(data) {
|
|
30
|
+
const identifier = resolveRegisterIdentifier(
|
|
31
|
+
data,
|
|
32
|
+
this.identifierField
|
|
33
|
+
);
|
|
34
|
+
const created = new this.authModel({
|
|
35
|
+
email: this.identifierField === "email" ? identifier : data.email != null ? normalizeIdentifier(data.email) : void 0,
|
|
36
|
+
username: this.identifierField === "username" ? identifier : data.username != null ? normalizeIdentifier(data.username) : void 0,
|
|
37
|
+
passwordHash: data.passwordHash,
|
|
38
|
+
emailVerified: data.emailVerified ?? false,
|
|
39
|
+
disabled: false
|
|
40
|
+
});
|
|
41
|
+
const saved = await created.save();
|
|
42
|
+
return toAccount(saved);
|
|
43
|
+
}
|
|
44
|
+
async findByEmail(email) {
|
|
45
|
+
const doc = await this.authModel.findOne({ email: normalizeIdentifier(email) }).exec();
|
|
46
|
+
return doc != null ? toAccount(doc) : null;
|
|
47
|
+
}
|
|
48
|
+
async findByIdentifier(identifier) {
|
|
49
|
+
const doc = await this.authModel.findOne(this.identifierQuery(identifier)).exec();
|
|
50
|
+
return doc != null ? toAccount(doc) : null;
|
|
51
|
+
}
|
|
52
|
+
async findByIdentifierWithSecrets(identifier) {
|
|
53
|
+
const doc = await this.authModel.findOne(this.identifierQuery(identifier)).select("+passwordHash +refreshTokenHash").exec();
|
|
54
|
+
return doc != null ? toAccountWithSecrets(doc) : null;
|
|
55
|
+
}
|
|
56
|
+
async findById(id) {
|
|
57
|
+
if (!Types.ObjectId.isValid(id)) return null;
|
|
58
|
+
const doc = await this.authModel.findById(id).exec();
|
|
59
|
+
return doc != null ? toAccount(doc) : null;
|
|
60
|
+
}
|
|
61
|
+
async findByIdWithSecrets(id) {
|
|
62
|
+
if (!Types.ObjectId.isValid(id)) return null;
|
|
63
|
+
const doc = await this.authModel.findById(id).select("+passwordHash +refreshTokenHash").exec();
|
|
64
|
+
return doc != null ? toAccountWithSecrets(doc) : null;
|
|
65
|
+
}
|
|
66
|
+
async updateRefreshTokenHash(id, hash) {
|
|
67
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
68
|
+
await this.authModel.updateOne({ _id: id }, { $set: { refreshTokenHash: hash } }).exec();
|
|
69
|
+
}
|
|
70
|
+
async updatePasswordHash(id, passwordHash) {
|
|
71
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
72
|
+
await this.authModel.updateOne({ _id: id }, { $set: { passwordHash } }).exec();
|
|
73
|
+
}
|
|
74
|
+
async setEmailVerificationToken(id, tokenHash, expiresAt) {
|
|
75
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
76
|
+
await this.authModel.updateOne(
|
|
77
|
+
{ _id: id },
|
|
78
|
+
{
|
|
79
|
+
$set: {
|
|
80
|
+
emailVerificationTokenHash: tokenHash,
|
|
81
|
+
emailVerificationExpiresAt: expiresAt
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
).exec();
|
|
85
|
+
}
|
|
86
|
+
async findByEmailVerificationTokenHash(tokenHash) {
|
|
87
|
+
const doc = await this.authModel.findOne({
|
|
88
|
+
emailVerificationTokenHash: tokenHash,
|
|
89
|
+
emailVerificationExpiresAt: { $gt: /* @__PURE__ */ new Date() }
|
|
90
|
+
}).select("+emailVerificationTokenHash").exec();
|
|
91
|
+
return doc != null ? toAccount(doc) : null;
|
|
92
|
+
}
|
|
93
|
+
async markEmailVerified(id) {
|
|
94
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
95
|
+
await this.authModel.updateOne(
|
|
96
|
+
{ _id: id },
|
|
97
|
+
{
|
|
98
|
+
$set: { emailVerified: true },
|
|
99
|
+
$unset: {
|
|
100
|
+
emailVerificationTokenHash: "",
|
|
101
|
+
emailVerificationExpiresAt: ""
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
).exec();
|
|
105
|
+
}
|
|
106
|
+
async setResetToken(id, tokenHash, expiresAt) {
|
|
107
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
108
|
+
await this.authModel.updateOne(
|
|
109
|
+
{ _id: id },
|
|
110
|
+
{ $set: { resetTokenHash: tokenHash, resetTokenExpiresAt: expiresAt } }
|
|
111
|
+
).exec();
|
|
112
|
+
}
|
|
113
|
+
async findByResetTokenHash(tokenHash) {
|
|
114
|
+
const doc = await this.authModel.findOne({
|
|
115
|
+
resetTokenHash: tokenHash,
|
|
116
|
+
resetTokenExpiresAt: { $gt: /* @__PURE__ */ new Date() }
|
|
117
|
+
}).select("+passwordHash +resetTokenHash").exec();
|
|
118
|
+
return doc != null ? toAccountWithSecrets(doc) : null;
|
|
119
|
+
}
|
|
120
|
+
async clearResetToken(id) {
|
|
121
|
+
if (!Types.ObjectId.isValid(id)) return;
|
|
122
|
+
await this.authModel.updateOne(
|
|
123
|
+
{ _id: id },
|
|
124
|
+
{ $unset: { resetTokenHash: "", resetTokenExpiresAt: "" } }
|
|
125
|
+
).exec();
|
|
126
|
+
}
|
|
127
|
+
identifierQuery(identifier) {
|
|
128
|
+
const normalized = normalizeIdentifier(identifier);
|
|
129
|
+
return this.identifierField === "email" ? { email: normalized } : { username: normalized };
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
MongoAuthRepository = __decorateClass([
|
|
133
|
+
Injectable()
|
|
134
|
+
], MongoAuthRepository);
|
|
135
|
+
var BASE_AUTH_ACCOUNT_MODEL = "AuthAccount";
|
|
136
|
+
var BaseAuthAccountSchema = class {
|
|
137
|
+
};
|
|
138
|
+
__decorateClass([
|
|
139
|
+
Prop({ required: false, trim: true, lowercase: true, index: true })
|
|
140
|
+
], BaseAuthAccountSchema.prototype, "email", 2);
|
|
141
|
+
__decorateClass([
|
|
142
|
+
Prop({ required: false, trim: true, lowercase: true, index: true })
|
|
143
|
+
], BaseAuthAccountSchema.prototype, "username", 2);
|
|
144
|
+
__decorateClass([
|
|
145
|
+
Prop({ required: true, select: false })
|
|
146
|
+
], BaseAuthAccountSchema.prototype, "passwordHash", 2);
|
|
147
|
+
__decorateClass([
|
|
148
|
+
Prop({ required: false, select: false })
|
|
149
|
+
], BaseAuthAccountSchema.prototype, "refreshTokenHash", 2);
|
|
150
|
+
__decorateClass([
|
|
151
|
+
Prop({ type: Boolean, default: false })
|
|
152
|
+
], BaseAuthAccountSchema.prototype, "emailVerified", 2);
|
|
153
|
+
__decorateClass([
|
|
154
|
+
Prop({ type: Boolean, default: false })
|
|
155
|
+
], BaseAuthAccountSchema.prototype, "disabled", 2);
|
|
156
|
+
__decorateClass([
|
|
157
|
+
Prop({ required: false, select: false })
|
|
158
|
+
], BaseAuthAccountSchema.prototype, "emailVerificationTokenHash", 2);
|
|
159
|
+
__decorateClass([
|
|
160
|
+
Prop({ required: false })
|
|
161
|
+
], BaseAuthAccountSchema.prototype, "emailVerificationExpiresAt", 2);
|
|
162
|
+
__decorateClass([
|
|
163
|
+
Prop({ required: false, select: false })
|
|
164
|
+
], BaseAuthAccountSchema.prototype, "resetTokenHash", 2);
|
|
165
|
+
__decorateClass([
|
|
166
|
+
Prop({ required: false })
|
|
167
|
+
], BaseAuthAccountSchema.prototype, "resetTokenExpiresAt", 2);
|
|
168
|
+
BaseAuthAccountSchema = __decorateClass([
|
|
169
|
+
Schema({
|
|
170
|
+
timestamps: true,
|
|
171
|
+
collection: "auth_accounts"
|
|
172
|
+
})
|
|
173
|
+
], BaseAuthAccountSchema);
|
|
174
|
+
var baseAuthAccountSchema = SchemaFactory.createForClass(BaseAuthAccountSchema);
|
|
175
|
+
baseAuthAccountSchema.index(
|
|
176
|
+
{ email: 1 },
|
|
177
|
+
{
|
|
178
|
+
unique: true,
|
|
179
|
+
partialFilterExpression: { email: { $type: "string" } }
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
baseAuthAccountSchema.index(
|
|
183
|
+
{ username: 1 },
|
|
184
|
+
{
|
|
185
|
+
unique: true,
|
|
186
|
+
partialFilterExpression: { username: { $type: "string" } }
|
|
187
|
+
}
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// src/mongo/mongo-auth.module.ts
|
|
191
|
+
var MongoAuthModule = class {
|
|
192
|
+
static forFeature(options) {
|
|
193
|
+
const name = options?.name ?? BASE_AUTH_ACCOUNT_MODEL;
|
|
194
|
+
const schema = options?.schema ?? baseAuthAccountSchema;
|
|
195
|
+
const identifierField = options?.identifierField ?? "email";
|
|
196
|
+
return {
|
|
197
|
+
module: MongoAuthModule,
|
|
198
|
+
imports: [MongooseModule.forFeature([{ name, schema }])],
|
|
199
|
+
providers: [
|
|
200
|
+
{
|
|
201
|
+
provide: MongoAuthRepository,
|
|
202
|
+
useFactory: (model, authOptions) => new MongoAuthRepository(
|
|
203
|
+
model,
|
|
204
|
+
authOptions?.identifierField ?? identifierField
|
|
205
|
+
),
|
|
206
|
+
inject: [
|
|
207
|
+
getModelToken(name),
|
|
208
|
+
{ token: AUTH_MODULE_OPTIONS, optional: true }
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
provide: AUTH_REPOSITORY,
|
|
213
|
+
useExisting: MongoAuthRepository
|
|
214
|
+
}
|
|
215
|
+
],
|
|
216
|
+
exports: [AUTH_REPOSITORY, MongooseModule]
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
};
|
|
220
|
+
MongoAuthModule = __decorateClass([
|
|
221
|
+
Module({})
|
|
222
|
+
], MongoAuthModule);
|
|
223
|
+
|
|
224
|
+
export { BASE_AUTH_ACCOUNT_MODEL, BaseAuthAccountSchema, MongoAuthModule, MongoAuthRepository, baseAuthAccountSchema };
|
|
225
|
+
//# sourceMappingURL=index.js.map
|
|
226
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/mongo/mongo-auth.repository.ts","../../src/mongo/schemas/base-auth-account.schema.ts","../../src/mongo/mongo-auth.module.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { Injectable } from \"@nestjs/common\";\nimport { Types, type Model } from \"mongoose\";\n\nimport type { AuthIdentifierField } from \"../interfaces/auth-config.interface\";\nimport type {\n AuthAccountWithSecrets,\n BaseAuthAccount,\n} from \"../interfaces/auth-hooks.interface\";\nimport type {\n CreateAccountData,\n IAuthRepository,\n} from \"../interfaces/auth-repository.interface\";\nimport {\n normalizeIdentifier,\n resolveRegisterIdentifier,\n} from \"../utils/identifier.util\";\nimport type { BaseAuthAccountDocument } from \"./schemas/base-auth-account.schema\";\n\nfunction toAccount(doc: BaseAuthAccountDocument): BaseAuthAccount {\n return {\n id: doc._id.toString(),\n ...(doc.email != null ? { email: doc.email } : {}),\n ...(doc.username != null ? { username: doc.username } : {}),\n emailVerified: doc.emailVerified,\n disabled: doc.disabled,\n createdAt: doc.createdAt,\n updatedAt: doc.updatedAt,\n };\n}\n\nfunction toAccountWithSecrets(\n doc: BaseAuthAccountDocument,\n): AuthAccountWithSecrets {\n return {\n ...toAccount(doc),\n passwordHash: doc.passwordHash,\n refreshTokenHash: doc.refreshTokenHash ?? null,\n };\n}\n\n@Injectable()\nexport class MongoAuthRepository implements IAuthRepository {\n constructor(\n private readonly authModel: Model<BaseAuthAccountDocument>,\n private readonly identifierField: AuthIdentifierField = \"email\",\n ) {}\n\n async create(data: CreateAccountData): Promise<BaseAuthAccount> {\n const identifier = resolveRegisterIdentifier(\n data,\n this.identifierField,\n );\n\n const created = new this.authModel({\n email:\n this.identifierField === \"email\"\n ? identifier\n : data.email != null\n ? normalizeIdentifier(data.email)\n : undefined,\n username:\n this.identifierField === \"username\"\n ? identifier\n : data.username != null\n ? normalizeIdentifier(data.username)\n : undefined,\n passwordHash: data.passwordHash,\n emailVerified: data.emailVerified ?? false,\n disabled: false,\n });\n\n const saved = await created.save();\n return toAccount(saved);\n }\n\n async findByEmail(email: string): Promise<BaseAuthAccount | null> {\n const doc = await this.authModel\n .findOne({ email: normalizeIdentifier(email) })\n .exec();\n return doc != null ? toAccount(doc) : null;\n }\n\n async findByIdentifier(identifier: string): Promise<BaseAuthAccount | null> {\n const doc = await this.authModel\n .findOne(this.identifierQuery(identifier))\n .exec();\n return doc != null ? toAccount(doc) : null;\n }\n\n async findByIdentifierWithSecrets(\n identifier: string,\n ): Promise<AuthAccountWithSecrets | null> {\n const doc = await this.authModel\n .findOne(this.identifierQuery(identifier))\n .select(\"+passwordHash +refreshTokenHash\")\n .exec();\n return doc != null ? toAccountWithSecrets(doc) : null;\n }\n\n async findById(id: string): Promise<BaseAuthAccount | null> {\n if (!Types.ObjectId.isValid(id)) return null;\n const doc = await this.authModel.findById(id).exec();\n return doc != null ? toAccount(doc) : null;\n }\n\n async findByIdWithSecrets(id: string): Promise<AuthAccountWithSecrets | null> {\n if (!Types.ObjectId.isValid(id)) return null;\n const doc = await this.authModel\n .findById(id)\n .select(\"+passwordHash +refreshTokenHash\")\n .exec();\n return doc != null ? toAccountWithSecrets(doc) : null;\n }\n\n async updateRefreshTokenHash(\n id: string,\n hash: string | null,\n ): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne({ _id: id }, { $set: { refreshTokenHash: hash } })\n .exec();\n }\n\n async updatePasswordHash(id: string, passwordHash: string): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne({ _id: id }, { $set: { passwordHash } })\n .exec();\n }\n\n async setEmailVerificationToken(\n id: string,\n tokenHash: string,\n expiresAt: Date,\n ): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne(\n { _id: id },\n {\n $set: {\n emailVerificationTokenHash: tokenHash,\n emailVerificationExpiresAt: expiresAt,\n },\n },\n )\n .exec();\n }\n\n async findByEmailVerificationTokenHash(\n tokenHash: string,\n ): Promise<BaseAuthAccount | null> {\n const doc = await this.authModel\n .findOne({\n emailVerificationTokenHash: tokenHash,\n emailVerificationExpiresAt: { $gt: new Date() },\n })\n .select(\"+emailVerificationTokenHash\")\n .exec();\n return doc != null ? toAccount(doc) : null;\n }\n\n async markEmailVerified(id: string): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne(\n { _id: id },\n {\n $set: { emailVerified: true },\n $unset: {\n emailVerificationTokenHash: \"\",\n emailVerificationExpiresAt: \"\",\n },\n },\n )\n .exec();\n }\n\n async setResetToken(\n id: string,\n tokenHash: string,\n expiresAt: Date,\n ): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne(\n { _id: id },\n { $set: { resetTokenHash: tokenHash, resetTokenExpiresAt: expiresAt } },\n )\n .exec();\n }\n\n async findByResetTokenHash(\n tokenHash: string,\n ): Promise<AuthAccountWithSecrets | null> {\n const doc = await this.authModel\n .findOne({\n resetTokenHash: tokenHash,\n resetTokenExpiresAt: { $gt: new Date() },\n })\n .select(\"+passwordHash +resetTokenHash\")\n .exec();\n return doc != null ? toAccountWithSecrets(doc) : null;\n }\n\n async clearResetToken(id: string): Promise<void> {\n if (!Types.ObjectId.isValid(id)) return;\n await this.authModel\n .updateOne(\n { _id: id },\n { $unset: { resetTokenHash: \"\", resetTokenExpiresAt: \"\" } },\n )\n .exec();\n }\n\n private identifierQuery(identifier: string) {\n const normalized = normalizeIdentifier(identifier);\n return this.identifierField === \"email\"\n ? { email: normalized }\n : { username: normalized };\n }\n}\n","import { Prop, Schema, SchemaFactory } from \"@nestjs/mongoose\";\nimport type { HydratedDocument } from \"mongoose\";\n\nexport const BASE_AUTH_ACCOUNT_MODEL = \"AuthAccount\";\n\n@Schema({\n timestamps: true,\n collection: \"auth_accounts\",\n})\nexport class BaseAuthAccountSchema {\n @Prop({ required: false, trim: true, lowercase: true, index: true })\n email?: string;\n\n @Prop({ required: false, trim: true, lowercase: true, index: true })\n username?: string;\n\n @Prop({ required: true, select: false })\n passwordHash!: string;\n\n @Prop({ required: false, select: false })\n refreshTokenHash?: string | null;\n\n @Prop({ type: Boolean, default: false })\n emailVerified!: boolean;\n\n @Prop({ type: Boolean, default: false })\n disabled!: boolean;\n\n @Prop({ required: false, select: false })\n emailVerificationTokenHash?: string;\n\n @Prop({ required: false })\n emailVerificationExpiresAt?: Date;\n\n @Prop({ required: false, select: false })\n resetTokenHash?: string;\n\n @Prop({ required: false })\n resetTokenExpiresAt?: Date;\n\n createdAt!: Date;\n updatedAt!: Date;\n}\n\nexport type BaseAuthAccountDocument = HydratedDocument<BaseAuthAccountSchema>;\n\nexport const baseAuthAccountSchema =\n SchemaFactory.createForClass(BaseAuthAccountSchema);\n\nbaseAuthAccountSchema.index(\n { email: 1 },\n {\n unique: true,\n partialFilterExpression: { email: { $type: \"string\" } },\n },\n);\n\nbaseAuthAccountSchema.index(\n { username: 1 },\n {\n unique: true,\n partialFilterExpression: { username: { $type: \"string\" } },\n },\n);\n","import { DynamicModule, Module } from \"@nestjs/common\";\nimport { getModelToken } from \"@nestjs/mongoose\";\nimport { MongooseModule } from \"@nestjs/mongoose\";\nimport type { Model, Schema } from \"mongoose\";\n\nimport { AUTH_MODULE_OPTIONS, AUTH_REPOSITORY } from \"../constants/tokens\";\nimport type { AuthIdentifierField } from \"../interfaces/auth-config.interface\";\nimport type { AuthModuleOptions } from \"../interfaces/auth-config.interface\";\nimport { MongoAuthRepository } from \"./mongo-auth.repository\";\nimport {\n BASE_AUTH_ACCOUNT_MODEL,\n baseAuthAccountSchema,\n type BaseAuthAccountDocument,\n} from \"./schemas/base-auth-account.schema\";\n\nexport interface MongoAuthFeatureOptions {\n /** Mongoose model name. Default: `AuthAccount`. */\n name?: string;\n /** Custom schema (e.g. extended with orgId, roleId). Default: base auth schema. */\n schema?: Schema;\n /** Identifier field when AuthModule options are not yet available. Default: `email`. */\n identifierField?: AuthIdentifierField;\n}\n\n@Module({})\nexport class MongoAuthModule {\n static forFeature(options?: MongoAuthFeatureOptions): DynamicModule {\n const name = options?.name ?? BASE_AUTH_ACCOUNT_MODEL;\n const schema = options?.schema ?? baseAuthAccountSchema;\n const identifierField = options?.identifierField ?? \"email\";\n\n return {\n module: MongoAuthModule,\n imports: [MongooseModule.forFeature([{ name, schema }])],\n providers: [\n {\n provide: MongoAuthRepository,\n useFactory: (\n model: Model<BaseAuthAccountDocument>,\n authOptions?: AuthModuleOptions,\n ) =>\n new MongoAuthRepository(\n model,\n authOptions?.identifierField ?? identifierField,\n ),\n inject: [\n getModelToken(name),\n { token: AUTH_MODULE_OPTIONS, optional: true },\n ],\n },\n {\n provide: AUTH_REPOSITORY,\n useExisting: MongoAuthRepository,\n },\n ],\n exports: [AUTH_REPOSITORY, MongooseModule],\n };\n }\n}"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aranzatech/aranza-auth",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Módulo de autenticación extensible para NestJS — JWT, refresh tokens, register/login y adapter MongoDB",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "AranzaTech",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"nestjs",
|
|
9
|
+
"auth",
|
|
10
|
+
"authentication",
|
|
11
|
+
"jwt",
|
|
12
|
+
"refresh-token",
|
|
13
|
+
"passport",
|
|
14
|
+
"mongodb",
|
|
15
|
+
"mongoose",
|
|
16
|
+
"aranzatech"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "git+https://github.com/aapa96/aranza-auth.git"
|
|
21
|
+
},
|
|
22
|
+
"bugs": {
|
|
23
|
+
"url": "https://github.com/aapa96/aranza-auth/issues"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/aapa96/aranza-auth#readme",
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public"
|
|
28
|
+
},
|
|
29
|
+
"type": "module",
|
|
30
|
+
"main": "./dist/index.cjs",
|
|
31
|
+
"module": "./dist/index.js",
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"exports": {
|
|
34
|
+
".": {
|
|
35
|
+
"types": "./dist/index.d.ts",
|
|
36
|
+
"import": "./dist/index.js",
|
|
37
|
+
"require": "./dist/index.cjs"
|
|
38
|
+
},
|
|
39
|
+
"./mongo": {
|
|
40
|
+
"types": "./dist/mongo/index.d.ts",
|
|
41
|
+
"import": "./dist/mongo/index.js",
|
|
42
|
+
"require": "./dist/mongo/index.cjs"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"dist",
|
|
47
|
+
"README.md",
|
|
48
|
+
"CHANGELOG.md"
|
|
49
|
+
],
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "tsup",
|
|
52
|
+
"dev": "tsup --watch",
|
|
53
|
+
"lint": "tsc --noEmit",
|
|
54
|
+
"test": "vitest run",
|
|
55
|
+
"test:watch": "vitest",
|
|
56
|
+
"test:coverage": "vitest run --coverage",
|
|
57
|
+
"deploy:npm": "npm install && npm run build && npm publish --access public"
|
|
58
|
+
},
|
|
59
|
+
"peerDependencies": {
|
|
60
|
+
"@nestjs/common": ">=11",
|
|
61
|
+
"@nestjs/core": ">=11",
|
|
62
|
+
"@nestjs/jwt": ">=11",
|
|
63
|
+
"@nestjs/mongoose": ">=11",
|
|
64
|
+
"@nestjs/passport": ">=11",
|
|
65
|
+
"bcryptjs": ">=2",
|
|
66
|
+
"class-transformer": ">=0.5",
|
|
67
|
+
"class-validator": ">=0.14",
|
|
68
|
+
"mongoose": ">=8",
|
|
69
|
+
"passport": ">=0.7",
|
|
70
|
+
"passport-jwt": ">=4",
|
|
71
|
+
"reflect-metadata": ">=0.2"
|
|
72
|
+
},
|
|
73
|
+
"peerDependenciesMeta": {
|
|
74
|
+
"@nestjs/mongoose": {
|
|
75
|
+
"optional": true
|
|
76
|
+
},
|
|
77
|
+
"mongoose": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
"devDependencies": {
|
|
82
|
+
"@nestjs/common": "^11.0.1",
|
|
83
|
+
"@nestjs/core": "^11.0.1",
|
|
84
|
+
"@nestjs/jwt": "^11.0.2",
|
|
85
|
+
"@nestjs/mongoose": "^11.0.4",
|
|
86
|
+
"@nestjs/passport": "^11.0.5",
|
|
87
|
+
"@types/bcryptjs": "^2.4.6",
|
|
88
|
+
"@types/passport-jwt": "^4.0.1",
|
|
89
|
+
"@vitest/coverage-v8": "^4.1.7",
|
|
90
|
+
"bcryptjs": "^3.0.3",
|
|
91
|
+
"class-transformer": "^0.5.1",
|
|
92
|
+
"class-validator": "^0.14.4",
|
|
93
|
+
"mongoose": "^8.19.1",
|
|
94
|
+
"passport": "^0.7.0",
|
|
95
|
+
"passport-jwt": "^4.0.1",
|
|
96
|
+
"reflect-metadata": "^0.2.2",
|
|
97
|
+
"rxjs": "^7.8.2",
|
|
98
|
+
"tsup": "^8.1.0",
|
|
99
|
+
"typescript": "^5.4.5",
|
|
100
|
+
"vitest": "^4.1.5"
|
|
101
|
+
}
|
|
102
|
+
}
|