@kb-labs/mcp-app 2.119.1-canary.ee03bc25d → 2.120.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/dist/bin.cjs +804 -9
- package/dist/bin.cjs.map +1 -1
- package/package.json +17 -17
package/dist/bin.cjs
CHANGED
|
@@ -87235,7 +87235,7 @@ var SignJWT = class extends ProduceJWT {
|
|
|
87235
87235
|
};
|
|
87236
87236
|
|
|
87237
87237
|
// ../../gateway/auth/dist/index.js
|
|
87238
|
-
__toESM(require_bcryptjs());
|
|
87238
|
+
var import_bcryptjs = __toESM(require_bcryptjs());
|
|
87239
87239
|
init_zod();
|
|
87240
87240
|
|
|
87241
87241
|
// ../../../core/contracts/dist/index.js
|
|
@@ -87784,6 +87784,549 @@ var AuthService = class {
|
|
|
87784
87784
|
};
|
|
87785
87785
|
}
|
|
87786
87786
|
};
|
|
87787
|
+
var COLLECTION = "users";
|
|
87788
|
+
var canonicalizeEmail = (raw) => raw.trim().toLowerCase();
|
|
87789
|
+
var docToUser = (doc) => ({
|
|
87790
|
+
userId: doc.userId,
|
|
87791
|
+
tenantId: doc.tenantId,
|
|
87792
|
+
email: doc.email,
|
|
87793
|
+
displayName: doc.displayName,
|
|
87794
|
+
status: doc.status,
|
|
87795
|
+
createdAt: doc.createdAt,
|
|
87796
|
+
updatedAt: doc.updatedAt
|
|
87797
|
+
});
|
|
87798
|
+
var UsersStore = class {
|
|
87799
|
+
constructor(docs) {
|
|
87800
|
+
this.docs = docs;
|
|
87801
|
+
}
|
|
87802
|
+
docs;
|
|
87803
|
+
initialised = null;
|
|
87804
|
+
async ensureSchema() {
|
|
87805
|
+
if (!this.initialised) {
|
|
87806
|
+
this.initialised = (async () => {
|
|
87807
|
+
await this.docs.ensureCollection(COLLECTION, {
|
|
87808
|
+
indexes: [
|
|
87809
|
+
// Logical primary key for "user inside tenant" lookups.
|
|
87810
|
+
{ path: ["tenantId", "email"], unique: true },
|
|
87811
|
+
// userId is the surrogate key callers reference everywhere.
|
|
87812
|
+
{ path: "userId", unique: true },
|
|
87813
|
+
{ path: "tenantId" }
|
|
87814
|
+
]
|
|
87815
|
+
});
|
|
87816
|
+
})();
|
|
87817
|
+
}
|
|
87818
|
+
await this.initialised;
|
|
87819
|
+
}
|
|
87820
|
+
async create(input) {
|
|
87821
|
+
await this.ensureSchema();
|
|
87822
|
+
const email3 = canonicalizeEmail(input.email);
|
|
87823
|
+
await this.docs.insertOne(COLLECTION, {
|
|
87824
|
+
userId: input.userId,
|
|
87825
|
+
tenantId: input.tenantId,
|
|
87826
|
+
email: email3,
|
|
87827
|
+
displayName: input.displayName,
|
|
87828
|
+
status: input.status
|
|
87829
|
+
});
|
|
87830
|
+
const created = await this.getById(input.userId);
|
|
87831
|
+
if (!created) {
|
|
87832
|
+
throw new Error(`users-store: inserted user ${input.userId} but read-back returned null`);
|
|
87833
|
+
}
|
|
87834
|
+
return created;
|
|
87835
|
+
}
|
|
87836
|
+
async getById(userId) {
|
|
87837
|
+
await this.ensureSchema();
|
|
87838
|
+
const doc = await this.docs.findById(COLLECTION, userId);
|
|
87839
|
+
if (doc && doc.userId === userId) {
|
|
87840
|
+
return docToUser(doc);
|
|
87841
|
+
}
|
|
87842
|
+
const [byField] = await this.docs.find(
|
|
87843
|
+
COLLECTION,
|
|
87844
|
+
{ userId: { $eq: userId } },
|
|
87845
|
+
{ limit: 1 }
|
|
87846
|
+
);
|
|
87847
|
+
return byField ? docToUser(byField) : null;
|
|
87848
|
+
}
|
|
87849
|
+
async findByEmailTenant(email3, tenantId) {
|
|
87850
|
+
await this.ensureSchema();
|
|
87851
|
+
const normalised = canonicalizeEmail(email3);
|
|
87852
|
+
const [doc] = await this.docs.find(
|
|
87853
|
+
COLLECTION,
|
|
87854
|
+
{ $and: [{ tenantId: { $eq: tenantId } }, { email: { $eq: normalised } }] },
|
|
87855
|
+
{ limit: 1 }
|
|
87856
|
+
);
|
|
87857
|
+
return doc ? docToUser(doc) : null;
|
|
87858
|
+
}
|
|
87859
|
+
async setStatus(userId, status) {
|
|
87860
|
+
await this.ensureSchema();
|
|
87861
|
+
const existing = await this.getById(userId);
|
|
87862
|
+
if (!existing) {
|
|
87863
|
+
throw new Error(`users-store: setStatus on unknown userId=${userId}`);
|
|
87864
|
+
}
|
|
87865
|
+
await this.docs.updateMany(
|
|
87866
|
+
COLLECTION,
|
|
87867
|
+
{ userId: { $eq: userId } },
|
|
87868
|
+
{ $set: { status } }
|
|
87869
|
+
);
|
|
87870
|
+
}
|
|
87871
|
+
/** List all users for a given tenant. */
|
|
87872
|
+
async listByTenant(tenantId) {
|
|
87873
|
+
await this.ensureSchema();
|
|
87874
|
+
const docs = await this.docs.find(
|
|
87875
|
+
COLLECTION,
|
|
87876
|
+
{ tenantId: { $eq: tenantId } }
|
|
87877
|
+
);
|
|
87878
|
+
return docs.map(docToUser);
|
|
87879
|
+
}
|
|
87880
|
+
async delete(userId) {
|
|
87881
|
+
await this.ensureSchema();
|
|
87882
|
+
await this.docs.deleteMany(COLLECTION, { userId: { $eq: userId } });
|
|
87883
|
+
}
|
|
87884
|
+
};
|
|
87885
|
+
var COLLECTION2 = "credentials";
|
|
87886
|
+
var docToCredential = (doc) => ({
|
|
87887
|
+
userId: doc.userId,
|
|
87888
|
+
providerId: doc.providerId,
|
|
87889
|
+
hash: doc.hash,
|
|
87890
|
+
createdAt: doc.createdAt,
|
|
87891
|
+
updatedAt: doc.updatedAt
|
|
87892
|
+
});
|
|
87893
|
+
var CredentialsStore = class {
|
|
87894
|
+
constructor(docs) {
|
|
87895
|
+
this.docs = docs;
|
|
87896
|
+
}
|
|
87897
|
+
docs;
|
|
87898
|
+
initialised = null;
|
|
87899
|
+
async ensureSchema() {
|
|
87900
|
+
if (!this.initialised) {
|
|
87901
|
+
this.initialised = (async () => {
|
|
87902
|
+
await this.docs.ensureCollection(COLLECTION2, {
|
|
87903
|
+
indexes: [
|
|
87904
|
+
// Logical primary key.
|
|
87905
|
+
{ path: ["userId", "providerId"], unique: true },
|
|
87906
|
+
// Cascade delete needs userId alone.
|
|
87907
|
+
{ path: "userId" }
|
|
87908
|
+
]
|
|
87909
|
+
});
|
|
87910
|
+
})();
|
|
87911
|
+
}
|
|
87912
|
+
await this.initialised;
|
|
87913
|
+
}
|
|
87914
|
+
async setCredential(input) {
|
|
87915
|
+
await this.ensureSchema();
|
|
87916
|
+
await this.docs.updateOne(
|
|
87917
|
+
COLLECTION2,
|
|
87918
|
+
{
|
|
87919
|
+
$and: [
|
|
87920
|
+
{ userId: { $eq: input.userId } },
|
|
87921
|
+
{ providerId: { $eq: input.providerId } }
|
|
87922
|
+
]
|
|
87923
|
+
},
|
|
87924
|
+
{
|
|
87925
|
+
$set: {
|
|
87926
|
+
userId: input.userId,
|
|
87927
|
+
providerId: input.providerId,
|
|
87928
|
+
hash: input.hash
|
|
87929
|
+
}
|
|
87930
|
+
},
|
|
87931
|
+
{ upsert: true }
|
|
87932
|
+
);
|
|
87933
|
+
}
|
|
87934
|
+
async getCredential(userId, providerId) {
|
|
87935
|
+
await this.ensureSchema();
|
|
87936
|
+
const [doc] = await this.docs.find(
|
|
87937
|
+
COLLECTION2,
|
|
87938
|
+
{
|
|
87939
|
+
$and: [
|
|
87940
|
+
{ userId: { $eq: userId } },
|
|
87941
|
+
{ providerId: { $eq: providerId } }
|
|
87942
|
+
]
|
|
87943
|
+
},
|
|
87944
|
+
{ limit: 1 }
|
|
87945
|
+
);
|
|
87946
|
+
return doc ? docToCredential(doc) : null;
|
|
87947
|
+
}
|
|
87948
|
+
async deleteCredential(userId, providerId) {
|
|
87949
|
+
await this.ensureSchema();
|
|
87950
|
+
await this.docs.deleteMany(COLLECTION2, {
|
|
87951
|
+
$and: [
|
|
87952
|
+
{ userId: { $eq: userId } },
|
|
87953
|
+
{ providerId: { $eq: providerId } }
|
|
87954
|
+
]
|
|
87955
|
+
});
|
|
87956
|
+
}
|
|
87957
|
+
/**
|
|
87958
|
+
* Cascade removal helper — call from the parent flow when a `User` is
|
|
87959
|
+
* deleted so we never leave dangling credentials in the store.
|
|
87960
|
+
*/
|
|
87961
|
+
async deleteAllForUser(userId) {
|
|
87962
|
+
await this.ensureSchema();
|
|
87963
|
+
await this.docs.deleteMany(COLLECTION2, {
|
|
87964
|
+
userId: { $eq: userId }
|
|
87965
|
+
});
|
|
87966
|
+
}
|
|
87967
|
+
};
|
|
87968
|
+
var COLLECTION3 = "memberships";
|
|
87969
|
+
var docToMembership = (doc) => ({
|
|
87970
|
+
userId: doc.userId,
|
|
87971
|
+
tenantId: doc.tenantId,
|
|
87972
|
+
groupId: doc.groupId,
|
|
87973
|
+
createdAt: doc.createdAt,
|
|
87974
|
+
updatedAt: doc.updatedAt
|
|
87975
|
+
});
|
|
87976
|
+
var MembershipsStore = class {
|
|
87977
|
+
constructor(docs) {
|
|
87978
|
+
this.docs = docs;
|
|
87979
|
+
}
|
|
87980
|
+
docs;
|
|
87981
|
+
initialised = null;
|
|
87982
|
+
async ensureSchema() {
|
|
87983
|
+
if (!this.initialised) {
|
|
87984
|
+
this.initialised = (async () => {
|
|
87985
|
+
await this.docs.ensureCollection(COLLECTION3, {
|
|
87986
|
+
indexes: [
|
|
87987
|
+
{ path: ["userId", "tenantId"], unique: true },
|
|
87988
|
+
{ path: "userId" },
|
|
87989
|
+
{ path: "tenantId" }
|
|
87990
|
+
]
|
|
87991
|
+
});
|
|
87992
|
+
})();
|
|
87993
|
+
}
|
|
87994
|
+
await this.initialised;
|
|
87995
|
+
}
|
|
87996
|
+
async addMembership(input) {
|
|
87997
|
+
await this.ensureSchema();
|
|
87998
|
+
await this.docs.insertOne(COLLECTION3, {
|
|
87999
|
+
userId: input.userId,
|
|
88000
|
+
tenantId: input.tenantId,
|
|
88001
|
+
groupId: input.groupId
|
|
88002
|
+
});
|
|
88003
|
+
}
|
|
88004
|
+
async setGroup(userId, tenantId, groupId) {
|
|
88005
|
+
await this.ensureSchema();
|
|
88006
|
+
const [existing] = await this.docs.find(
|
|
88007
|
+
COLLECTION3,
|
|
88008
|
+
{ $and: [{ userId: { $eq: userId } }, { tenantId: { $eq: tenantId } }] },
|
|
88009
|
+
{ limit: 1 }
|
|
88010
|
+
);
|
|
88011
|
+
if (!existing) {
|
|
88012
|
+
throw new Error(
|
|
88013
|
+
`memberships-store: setGroup on missing membership userId=${userId} tenantId=${tenantId}`
|
|
88014
|
+
);
|
|
88015
|
+
}
|
|
88016
|
+
await this.docs.updateMany(
|
|
88017
|
+
COLLECTION3,
|
|
88018
|
+
{ $and: [{ userId: { $eq: userId } }, { tenantId: { $eq: tenantId } }] },
|
|
88019
|
+
{ $set: { groupId } }
|
|
88020
|
+
);
|
|
88021
|
+
}
|
|
88022
|
+
async listByUser(userId) {
|
|
88023
|
+
await this.ensureSchema();
|
|
88024
|
+
const docs = await this.docs.find(
|
|
88025
|
+
COLLECTION3,
|
|
88026
|
+
{ userId: { $eq: userId } },
|
|
88027
|
+
{ sort: { tenantId: 1 } }
|
|
88028
|
+
);
|
|
88029
|
+
return docs.map(docToMembership);
|
|
88030
|
+
}
|
|
88031
|
+
async listByTenant(tenantId) {
|
|
88032
|
+
await this.ensureSchema();
|
|
88033
|
+
const docs = await this.docs.find(
|
|
88034
|
+
COLLECTION3,
|
|
88035
|
+
{ tenantId: { $eq: tenantId } },
|
|
88036
|
+
{ sort: { userId: 1 } }
|
|
88037
|
+
);
|
|
88038
|
+
return docs.map(docToMembership);
|
|
88039
|
+
}
|
|
88040
|
+
async removeMembership(userId, tenantId) {
|
|
88041
|
+
await this.ensureSchema();
|
|
88042
|
+
await this.docs.deleteMany(
|
|
88043
|
+
COLLECTION3,
|
|
88044
|
+
{ $and: [{ userId: { $eq: userId } }, { tenantId: { $eq: tenantId } }] }
|
|
88045
|
+
);
|
|
88046
|
+
}
|
|
88047
|
+
/** Cascade hook — call when a `User` is deleted. */
|
|
88048
|
+
async removeAllForUser(userId) {
|
|
88049
|
+
await this.ensureSchema();
|
|
88050
|
+
await this.docs.deleteMany(COLLECTION3, { userId: { $eq: userId } });
|
|
88051
|
+
}
|
|
88052
|
+
};
|
|
88053
|
+
var FAMILIES = "session_families";
|
|
88054
|
+
var REFRESHES = "refresh_tokens";
|
|
88055
|
+
var RefreshNotFoundError = class extends Error {
|
|
88056
|
+
constructor() {
|
|
88057
|
+
super("refresh token not found");
|
|
88058
|
+
this.name = "RefreshNotFoundError";
|
|
88059
|
+
}
|
|
88060
|
+
};
|
|
88061
|
+
var RefreshExpiredError = class extends Error {
|
|
88062
|
+
constructor() {
|
|
88063
|
+
super("refresh token expired");
|
|
88064
|
+
this.name = "RefreshExpiredError";
|
|
88065
|
+
}
|
|
88066
|
+
};
|
|
88067
|
+
var RefreshReuseDetectedError = class extends Error {
|
|
88068
|
+
constructor(familyId) {
|
|
88069
|
+
super(`refresh token reuse detected (family ${familyId} killed)`);
|
|
88070
|
+
this.familyId = familyId;
|
|
88071
|
+
this.name = "RefreshReuseDetectedError";
|
|
88072
|
+
}
|
|
88073
|
+
familyId;
|
|
88074
|
+
};
|
|
88075
|
+
var newId = (bytes = 16) => crypto4.randomBytes(bytes).toString("base64url");
|
|
88076
|
+
var SessionsStore = class {
|
|
88077
|
+
constructor(docs, opts) {
|
|
88078
|
+
this.docs = docs;
|
|
88079
|
+
this.now = opts.now ?? Date.now;
|
|
88080
|
+
this.refreshTtlMs = opts.refreshTtlMs;
|
|
88081
|
+
this.graceWindowMs = opts.graceWindowMs;
|
|
88082
|
+
}
|
|
88083
|
+
docs;
|
|
88084
|
+
initialised = null;
|
|
88085
|
+
now;
|
|
88086
|
+
refreshTtlMs;
|
|
88087
|
+
graceWindowMs;
|
|
88088
|
+
async ensureSchema() {
|
|
88089
|
+
if (!this.initialised) {
|
|
88090
|
+
this.initialised = (async () => {
|
|
88091
|
+
await this.docs.ensureCollection(FAMILIES, {
|
|
88092
|
+
indexes: [
|
|
88093
|
+
{ path: "familyId", unique: true },
|
|
88094
|
+
{ path: "userId" }
|
|
88095
|
+
]
|
|
88096
|
+
});
|
|
88097
|
+
await this.docs.ensureCollection(REFRESHES, {
|
|
88098
|
+
indexes: [
|
|
88099
|
+
{ path: "jti", unique: true },
|
|
88100
|
+
{ path: "familyId" },
|
|
88101
|
+
{ path: "userId" },
|
|
88102
|
+
// Best-effort sweep; `rotateRefresh` checks expiresAt > now (CD-9).
|
|
88103
|
+
{ path: "expiresAt", ttl: 0 }
|
|
88104
|
+
]
|
|
88105
|
+
});
|
|
88106
|
+
})();
|
|
88107
|
+
}
|
|
88108
|
+
await this.initialised;
|
|
88109
|
+
}
|
|
88110
|
+
async createSession(input) {
|
|
88111
|
+
await this.ensureSchema();
|
|
88112
|
+
const familyId = newId();
|
|
88113
|
+
const refreshJti = newId();
|
|
88114
|
+
const now = this.now();
|
|
88115
|
+
const refreshExpiresAt = now + this.refreshTtlMs;
|
|
88116
|
+
await this.docs.transaction(async (tx) => {
|
|
88117
|
+
await tx.insertOne(FAMILIES, {
|
|
88118
|
+
familyId,
|
|
88119
|
+
userId: input.userId,
|
|
88120
|
+
tenantId: input.tenantId,
|
|
88121
|
+
lastUsedAt: now,
|
|
88122
|
+
userAgent: input.deviceCtx.userAgent,
|
|
88123
|
+
ipFirst: input.deviceCtx.ip
|
|
88124
|
+
});
|
|
88125
|
+
await tx.insertOne(REFRESHES, {
|
|
88126
|
+
jti: refreshJti,
|
|
88127
|
+
familyId,
|
|
88128
|
+
userId: input.userId,
|
|
88129
|
+
expiresAt: refreshExpiresAt
|
|
88130
|
+
});
|
|
88131
|
+
});
|
|
88132
|
+
return { familyId, refreshJti, refreshExpiresAt };
|
|
88133
|
+
}
|
|
88134
|
+
/**
|
|
88135
|
+
* Consume `oldJti` and issue a new refresh in the same family.
|
|
88136
|
+
*
|
|
88137
|
+
* Throws:
|
|
88138
|
+
* - `RefreshNotFoundError` if jti is unknown or its family was revoked.
|
|
88139
|
+
* - `RefreshExpiredError` if jti is past `expiresAt` (family stays alive).
|
|
88140
|
+
* - `RefreshReuseDetectedError` if jti was already consumed and the
|
|
88141
|
+
* grace window has passed — family is killed before throwing.
|
|
88142
|
+
*
|
|
88143
|
+
* On grace-window retry (same already-consumed jti within
|
|
88144
|
+
* `graceWindowMs`) the previously-issued replacement is returned and
|
|
88145
|
+
* no new refresh is created.
|
|
88146
|
+
*/
|
|
88147
|
+
async rotateRefresh(oldJti) {
|
|
88148
|
+
await this.ensureSchema();
|
|
88149
|
+
const now = this.now();
|
|
88150
|
+
const newJti = newId();
|
|
88151
|
+
const newExpiresAt = now + this.refreshTtlMs;
|
|
88152
|
+
const outcome = await this.docs.transaction(async (tx) => {
|
|
88153
|
+
const [current] = await tx.find(
|
|
88154
|
+
REFRESHES,
|
|
88155
|
+
{ jti: { $eq: oldJti } },
|
|
88156
|
+
{ limit: 1 }
|
|
88157
|
+
);
|
|
88158
|
+
if (!current) {
|
|
88159
|
+
return { tag: "not-found" };
|
|
88160
|
+
}
|
|
88161
|
+
if (current.consumedAt !== void 0) {
|
|
88162
|
+
const elapsed = now - current.consumedAt;
|
|
88163
|
+
if (elapsed <= this.graceWindowMs && current.replacedBy) {
|
|
88164
|
+
const [replacement] = await tx.find(
|
|
88165
|
+
REFRESHES,
|
|
88166
|
+
{ jti: { $eq: current.replacedBy } },
|
|
88167
|
+
{ limit: 1 }
|
|
88168
|
+
);
|
|
88169
|
+
if (replacement) {
|
|
88170
|
+
return {
|
|
88171
|
+
tag: "ok",
|
|
88172
|
+
result: {
|
|
88173
|
+
newJti: replacement.jti,
|
|
88174
|
+
familyId: current.familyId,
|
|
88175
|
+
newRefreshExpiresAt: replacement.expiresAt
|
|
88176
|
+
}
|
|
88177
|
+
};
|
|
88178
|
+
}
|
|
88179
|
+
}
|
|
88180
|
+
if (current.expiresAt <= now) {
|
|
88181
|
+
return { tag: "expired" };
|
|
88182
|
+
}
|
|
88183
|
+
return { tag: "reuse", familyId: current.familyId };
|
|
88184
|
+
}
|
|
88185
|
+
if (current.expiresAt <= now) {
|
|
88186
|
+
return { tag: "expired" };
|
|
88187
|
+
}
|
|
88188
|
+
await tx.updateMany(
|
|
88189
|
+
REFRESHES,
|
|
88190
|
+
{ jti: { $eq: oldJti } },
|
|
88191
|
+
{ $set: { consumedAt: now, replacedBy: newJti } }
|
|
88192
|
+
);
|
|
88193
|
+
await tx.insertOne(REFRESHES, {
|
|
88194
|
+
jti: newJti,
|
|
88195
|
+
familyId: current.familyId,
|
|
88196
|
+
userId: current.userId,
|
|
88197
|
+
expiresAt: newExpiresAt
|
|
88198
|
+
});
|
|
88199
|
+
await tx.updateMany(
|
|
88200
|
+
FAMILIES,
|
|
88201
|
+
{ familyId: { $eq: current.familyId } },
|
|
88202
|
+
{ $set: { lastUsedAt: now } }
|
|
88203
|
+
);
|
|
88204
|
+
return {
|
|
88205
|
+
tag: "ok",
|
|
88206
|
+
result: { newJti, familyId: current.familyId, newRefreshExpiresAt: newExpiresAt }
|
|
88207
|
+
};
|
|
88208
|
+
});
|
|
88209
|
+
switch (outcome.tag) {
|
|
88210
|
+
case "ok":
|
|
88211
|
+
return outcome.result;
|
|
88212
|
+
case "not-found":
|
|
88213
|
+
throw new RefreshNotFoundError();
|
|
88214
|
+
case "expired":
|
|
88215
|
+
throw new RefreshExpiredError();
|
|
88216
|
+
case "reuse":
|
|
88217
|
+
await this.revokeFamily(outcome.familyId);
|
|
88218
|
+
throw new RefreshReuseDetectedError(outcome.familyId);
|
|
88219
|
+
}
|
|
88220
|
+
}
|
|
88221
|
+
async revokeFamily(familyId) {
|
|
88222
|
+
await this.ensureSchema();
|
|
88223
|
+
await this.docs.transaction(async (tx) => {
|
|
88224
|
+
await tx.deleteMany(REFRESHES, { familyId: { $eq: familyId } });
|
|
88225
|
+
await tx.deleteMany(FAMILIES, { familyId: { $eq: familyId } });
|
|
88226
|
+
});
|
|
88227
|
+
}
|
|
88228
|
+
async revokeAllUserSessions(userId) {
|
|
88229
|
+
await this.ensureSchema();
|
|
88230
|
+
await this.docs.transaction(async (tx) => {
|
|
88231
|
+
await tx.deleteMany(REFRESHES, { userId: { $eq: userId } });
|
|
88232
|
+
await tx.deleteMany(FAMILIES, { userId: { $eq: userId } });
|
|
88233
|
+
});
|
|
88234
|
+
}
|
|
88235
|
+
async revokeAllUserSessionsExcept(userId, exceptFamilyId) {
|
|
88236
|
+
await this.ensureSchema();
|
|
88237
|
+
await this.docs.transaction(async (tx) => {
|
|
88238
|
+
const families = await tx.find(FAMILIES, { userId: { $eq: userId } });
|
|
88239
|
+
const targets = families.filter((f) => f.familyId !== exceptFamilyId).map((f) => f.familyId);
|
|
88240
|
+
if (targets.length === 0) {
|
|
88241
|
+
return;
|
|
88242
|
+
}
|
|
88243
|
+
await tx.deleteMany(
|
|
88244
|
+
REFRESHES,
|
|
88245
|
+
{ $and: [{ userId: { $eq: userId } }, { familyId: { $in: targets } }] }
|
|
88246
|
+
);
|
|
88247
|
+
await tx.deleteMany(
|
|
88248
|
+
FAMILIES,
|
|
88249
|
+
{ $and: [{ userId: { $eq: userId } }, { familyId: { $in: targets } }] }
|
|
88250
|
+
);
|
|
88251
|
+
});
|
|
88252
|
+
}
|
|
88253
|
+
async listFamiliesByUser(userId) {
|
|
88254
|
+
await this.ensureSchema();
|
|
88255
|
+
const docs = await this.docs.find(
|
|
88256
|
+
FAMILIES,
|
|
88257
|
+
{ userId: { $eq: userId } },
|
|
88258
|
+
{ sort: { lastUsedAt: -1 } }
|
|
88259
|
+
);
|
|
88260
|
+
return docs.map((d) => ({
|
|
88261
|
+
familyId: d.familyId,
|
|
88262
|
+
userId: d.userId,
|
|
88263
|
+
tenantId: d.tenantId,
|
|
88264
|
+
createdAt: d.createdAt,
|
|
88265
|
+
lastUsedAt: d.lastUsedAt,
|
|
88266
|
+
userAgent: d.userAgent,
|
|
88267
|
+
ipFirst: d.ipFirst
|
|
88268
|
+
}));
|
|
88269
|
+
}
|
|
88270
|
+
};
|
|
88271
|
+
var noopLogger = {
|
|
88272
|
+
warn: () => void 0,
|
|
88273
|
+
info: () => void 0,
|
|
88274
|
+
error: () => void 0
|
|
88275
|
+
};
|
|
88276
|
+
var sha1Hex = (input) => crypto4.createHash("sha1").update(input).digest("hex").toUpperCase();
|
|
88277
|
+
var queryHibp = async (plain, doFetch) => {
|
|
88278
|
+
const hash2 = sha1Hex(plain);
|
|
88279
|
+
const prefix = hash2.slice(0, 5);
|
|
88280
|
+
const wantedSuffix = hash2.slice(5);
|
|
88281
|
+
const resp = await doFetch(`https://api.pwnedpasswords.com/range/${prefix}`);
|
|
88282
|
+
if (!resp.ok) {
|
|
88283
|
+
throw new Error(`HIBP ${resp.status}`);
|
|
88284
|
+
}
|
|
88285
|
+
const text = await resp.text();
|
|
88286
|
+
for (const line of text.split(/\r?\n/)) {
|
|
88287
|
+
const trimmed = line.trim();
|
|
88288
|
+
if (!trimmed) {
|
|
88289
|
+
continue;
|
|
88290
|
+
}
|
|
88291
|
+
const sepIdx = trimmed.indexOf(":");
|
|
88292
|
+
if (sepIdx < 0) {
|
|
88293
|
+
continue;
|
|
88294
|
+
}
|
|
88295
|
+
const suffix = trimmed.slice(0, sepIdx).toUpperCase();
|
|
88296
|
+
if (suffix === wantedSuffix) {
|
|
88297
|
+
const count = Number.parseInt(trimmed.slice(sepIdx + 1), 10);
|
|
88298
|
+
return Number.isFinite(count) ? count : 1;
|
|
88299
|
+
}
|
|
88300
|
+
}
|
|
88301
|
+
return 0;
|
|
88302
|
+
};
|
|
88303
|
+
var createPasswordPolicy = (opts) => {
|
|
88304
|
+
const log3 = opts.logger ?? noopLogger;
|
|
88305
|
+
const doFetch = opts.fetch ?? fetch;
|
|
88306
|
+
return {
|
|
88307
|
+
async validate(plain) {
|
|
88308
|
+
if (plain.length < opts.minLength) {
|
|
88309
|
+
return { ok: false, reason: "too_short" };
|
|
88310
|
+
}
|
|
88311
|
+
if (plain.length > opts.maxLength) {
|
|
88312
|
+
return { ok: false, reason: "too_long" };
|
|
88313
|
+
}
|
|
88314
|
+
if (!opts.hibpEnabled) {
|
|
88315
|
+
return { ok: true };
|
|
88316
|
+
}
|
|
88317
|
+
try {
|
|
88318
|
+
const count = await queryHibp(plain, doFetch);
|
|
88319
|
+
if (count > 0) {
|
|
88320
|
+
return { ok: false, reason: "pwned" };
|
|
88321
|
+
}
|
|
88322
|
+
return { ok: true };
|
|
88323
|
+
} catch (err) {
|
|
88324
|
+
log3.warn("password-policy: HIBP unavailable, accepting password", { err: String(err) });
|
|
88325
|
+
return { ok: true, warning: "hibp_unavailable" };
|
|
88326
|
+
}
|
|
88327
|
+
}
|
|
88328
|
+
};
|
|
88329
|
+
};
|
|
87787
88330
|
external_exports.object({
|
|
87788
88331
|
type: external_exports.literal("oidc"),
|
|
87789
88332
|
id: external_exports.string().min(1),
|
|
@@ -87802,6 +88345,54 @@ external_exports.object({
|
|
|
87802
88345
|
}).passthrough();
|
|
87803
88346
|
Object.freeze(Object.values(PERMISSIONS));
|
|
87804
88347
|
Object.freeze([PERMISSIONS.MACHINE_REGISTER]);
|
|
88348
|
+
var ResetAdminError = class extends Error {
|
|
88349
|
+
constructor(code, message2) {
|
|
88350
|
+
super(message2);
|
|
88351
|
+
this.code = code;
|
|
88352
|
+
this.name = "ResetAdminError";
|
|
88353
|
+
}
|
|
88354
|
+
code;
|
|
88355
|
+
};
|
|
88356
|
+
var resetAdmin = async (opts) => {
|
|
88357
|
+
const { users, credentials, memberships, sessions, tenantId, logger: logger2 } = opts;
|
|
88358
|
+
const email3 = canonicalizeEmail(opts.email);
|
|
88359
|
+
if (!email3 || !tenantId) {
|
|
88360
|
+
throw new ResetAdminError("invalid_input", "resetAdmin: email and tenantId are required");
|
|
88361
|
+
}
|
|
88362
|
+
const policy = await opts.passwordPolicy.validate(opts.password);
|
|
88363
|
+
if (!policy.ok) {
|
|
88364
|
+
throw new ResetAdminError("weak_password", `password rejected by policy: ${policy.reason}`);
|
|
88365
|
+
}
|
|
88366
|
+
const repairs = [];
|
|
88367
|
+
const existing = await users.findByEmailTenant(email3, tenantId);
|
|
88368
|
+
let userId;
|
|
88369
|
+
if (!existing) {
|
|
88370
|
+
userId = crypto4.randomUUID();
|
|
88371
|
+
await users.create({ userId, tenantId, email: email3, status: "active" });
|
|
88372
|
+
repairs.push("user-created");
|
|
88373
|
+
} else {
|
|
88374
|
+
userId = existing.userId;
|
|
88375
|
+
if (existing.status !== "active") {
|
|
88376
|
+
await users.setStatus(userId, "active");
|
|
88377
|
+
repairs.push("user-reactivated");
|
|
88378
|
+
}
|
|
88379
|
+
}
|
|
88380
|
+
const hash2 = await import_bcryptjs.default.hash(opts.password, opts.bcryptCost);
|
|
88381
|
+
await credentials.setCredential({ userId, providerId: "email-password", hash: hash2 });
|
|
88382
|
+
repairs.push("credential-set");
|
|
88383
|
+
const membership = (await memberships.listByUser(userId)).find((m) => m.tenantId === tenantId);
|
|
88384
|
+
if (!membership) {
|
|
88385
|
+
await memberships.addMembership({ userId, tenantId, groupId: "tenant-admin" });
|
|
88386
|
+
repairs.push("membership-created");
|
|
88387
|
+
} else if (membership.groupId !== "tenant-admin") {
|
|
88388
|
+
await memberships.setGroup(userId, tenantId, "tenant-admin");
|
|
88389
|
+
repairs.push("membership-promoted");
|
|
88390
|
+
}
|
|
88391
|
+
await sessions.revokeAllUserSessions(userId);
|
|
88392
|
+
const outcome = existing ? "reset" : "created";
|
|
88393
|
+
logger2.info("reset-admin: admin account restored", { userId, tenantId, outcome, repairs });
|
|
88394
|
+
return { userId, tenantId, email: email3, outcome, repairs };
|
|
88395
|
+
};
|
|
87805
88396
|
|
|
87806
88397
|
// src/mcp/auth.ts
|
|
87807
88398
|
var DEV_JWT_SECRET = "dev-insecure-secret-change-me";
|
|
@@ -110578,7 +111169,7 @@ function validateSegments(segs) {
|
|
|
110578
111169
|
}
|
|
110579
111170
|
return null;
|
|
110580
111171
|
}
|
|
110581
|
-
var
|
|
111172
|
+
var noopLogger2 = {
|
|
110582
111173
|
debug: () => {
|
|
110583
111174
|
},
|
|
110584
111175
|
info: () => {
|
|
@@ -110587,13 +111178,13 @@ var noopLogger = {
|
|
|
110587
111178
|
},
|
|
110588
111179
|
error: () => {
|
|
110589
111180
|
},
|
|
110590
|
-
child: () =>
|
|
111181
|
+
child: () => noopLogger2
|
|
110591
111182
|
};
|
|
110592
111183
|
var TrieBackedRegistry = class {
|
|
110593
111184
|
systemRouter = new TrieRouter();
|
|
110594
111185
|
pluginRouter = new TrieRouter();
|
|
110595
111186
|
partial = false;
|
|
110596
|
-
logger =
|
|
111187
|
+
logger = noopLogger2;
|
|
110597
111188
|
// ─── Logger ────────────────────────────────────────────────────────────────
|
|
110598
111189
|
setLogger(logger2) {
|
|
110599
111190
|
this.logger = logger2;
|
|
@@ -110777,31 +111368,31 @@ defineSystemCommand({
|
|
|
110777
111368
|
const cliCtx = ctx.hostContext?.host === "cli" ? ctx.hostContext : void 0;
|
|
110778
111369
|
const cliVersion = cliCtx?.cliVersion ?? process.env.CLI_VERSION ?? "0.0.0";
|
|
110779
111370
|
const nodeVersion = process.version;
|
|
110780
|
-
const
|
|
111371
|
+
const platform12 = `${process.platform} ${process.arch}`;
|
|
110781
111372
|
ctx.platform?.logger?.info("Version command executed", {
|
|
110782
111373
|
version: cliVersion,
|
|
110783
111374
|
nodeVersion,
|
|
110784
|
-
platform:
|
|
111375
|
+
platform: platform12
|
|
110785
111376
|
});
|
|
110786
111377
|
if (!flags.json) {
|
|
110787
111378
|
ctx.ui?.write(`KB Labs CLI v${cliVersion}
|
|
110788
111379
|
`);
|
|
110789
111380
|
ctx.ui?.write(`Node: ${nodeVersion}
|
|
110790
111381
|
`);
|
|
110791
|
-
ctx.ui?.write(`Platform: ${
|
|
111382
|
+
ctx.ui?.write(`Platform: ${platform12}
|
|
110792
111383
|
`);
|
|
110793
111384
|
} else {
|
|
110794
111385
|
ctx.ui?.json({
|
|
110795
111386
|
version: cliVersion,
|
|
110796
111387
|
nodeVersion,
|
|
110797
|
-
platform:
|
|
111388
|
+
platform: platform12
|
|
110798
111389
|
});
|
|
110799
111390
|
}
|
|
110800
111391
|
return {
|
|
110801
111392
|
ok: true,
|
|
110802
111393
|
version: cliVersion,
|
|
110803
111394
|
nodeVersion,
|
|
110804
|
-
platform:
|
|
111395
|
+
platform: platform12
|
|
110805
111396
|
};
|
|
110806
111397
|
}
|
|
110807
111398
|
});
|
|
@@ -113176,6 +113767,210 @@ defineSystemCommand({
|
|
|
113176
113767
|
return { ok: true, clientId: data.clientId, clientSecret: data.clientSecret, hostId: data.hostId };
|
|
113177
113768
|
}
|
|
113178
113769
|
});
|
|
113770
|
+
var DEFAULT_TENANT = "kblabs-cloud";
|
|
113771
|
+
var DEFAULT_GATEWAY_HOST = "127.0.0.1";
|
|
113772
|
+
var DEFAULT_GATEWAY_PORT = 4e3;
|
|
113773
|
+
var PROBE_TIMEOUT_MS = 500;
|
|
113774
|
+
function isRecord3(v) {
|
|
113775
|
+
return typeof v === "object" && v !== null;
|
|
113776
|
+
}
|
|
113777
|
+
function gatewaySlice(config2) {
|
|
113778
|
+
const gw = config2?.gateway;
|
|
113779
|
+
return isRecord3(gw) ? gw : {};
|
|
113780
|
+
}
|
|
113781
|
+
async function readStdin() {
|
|
113782
|
+
const chunks = [];
|
|
113783
|
+
for await (const chunk of process.stdin) {
|
|
113784
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
113785
|
+
}
|
|
113786
|
+
return Buffer.concat(chunks).toString("utf-8").replace(/\r?\n$/, "");
|
|
113787
|
+
}
|
|
113788
|
+
function isPortListening(host, port) {
|
|
113789
|
+
return new Promise((resolve9) => {
|
|
113790
|
+
const socket = net.connect({ host, port });
|
|
113791
|
+
const done = (listening) => {
|
|
113792
|
+
socket.destroy();
|
|
113793
|
+
resolve9(listening);
|
|
113794
|
+
};
|
|
113795
|
+
socket.setTimeout(PROBE_TIMEOUT_MS, () => done(false));
|
|
113796
|
+
socket.once("connect", () => done(true));
|
|
113797
|
+
socket.once("error", () => done(false));
|
|
113798
|
+
});
|
|
113799
|
+
}
|
|
113800
|
+
async function planRepairs(existing, tenantId, memberships) {
|
|
113801
|
+
if (!existing) {
|
|
113802
|
+
return ["user-created", "credential-set", "membership-created"];
|
|
113803
|
+
}
|
|
113804
|
+
const repairs = [];
|
|
113805
|
+
if (existing.status !== "active") {
|
|
113806
|
+
repairs.push("user-reactivated");
|
|
113807
|
+
}
|
|
113808
|
+
repairs.push("credential-set");
|
|
113809
|
+
const membership = (await memberships.listByUser(existing.userId)).find((m) => m.tenantId === tenantId);
|
|
113810
|
+
if (!membership) {
|
|
113811
|
+
repairs.push("membership-created");
|
|
113812
|
+
} else if (membership.groupId !== "tenant-admin") {
|
|
113813
|
+
repairs.push("membership-promoted");
|
|
113814
|
+
}
|
|
113815
|
+
return repairs;
|
|
113816
|
+
}
|
|
113817
|
+
function printDryRun(ctx, existing, dry) {
|
|
113818
|
+
ctx.ui?.write?.(`Dry run \u2014 nothing changed.
|
|
113819
|
+
Admin: ${dry.email} (tenant ${dry.tenantId})
|
|
113820
|
+
`);
|
|
113821
|
+
ctx.ui?.write?.(existing ? ` State: exists, status=${existing.status}
|
|
113822
|
+
` : " State: does not exist\n");
|
|
113823
|
+
ctx.ui?.write?.(` Would: ${dry.repairs?.join(", ")}, revoke all sessions
|
|
113824
|
+
`);
|
|
113825
|
+
ctx.ui?.write?.("Re-run with --yes and --password-stdin or --generate to apply.\n");
|
|
113826
|
+
}
|
|
113827
|
+
function printApplied(ctx, out) {
|
|
113828
|
+
ctx.ui?.write?.(`Admin ${out.outcome === "created" ? "created" : "restored"}: ${out.email} (tenant ${out.tenantId})
|
|
113829
|
+
`);
|
|
113830
|
+
ctx.ui?.write?.(` Changes: ${out.repairs?.join(", ")}; all sessions revoked
|
|
113831
|
+
`);
|
|
113832
|
+
if (out.generatedPassword) {
|
|
113833
|
+
ctx.ui?.write?.(` Password: ${out.generatedPassword}
|
|
113834
|
+
Save it now \u2014 it is not shown again.
|
|
113835
|
+
`);
|
|
113836
|
+
}
|
|
113837
|
+
}
|
|
113838
|
+
defineSystemCommand({
|
|
113839
|
+
name: "reset-admin",
|
|
113840
|
+
description: "Recover the tenant admin account (reset password, reactivate, restore role)",
|
|
113841
|
+
longDescription: "Offline recovery of the tenant admin against the platform database: creates the admin if missing, reactivates it, sets a new password, restores the tenant-admin membership and revokes all its sessions. Dry run unless --yes is given. The password is read from stdin (--password-stdin) or generated (--generate); it is never accepted as an argument.",
|
|
113842
|
+
category: "auth",
|
|
113843
|
+
examples: [
|
|
113844
|
+
"kb auth reset-admin",
|
|
113845
|
+
"kb auth reset-admin --generate --yes",
|
|
113846
|
+
'printf %s "$NEW_PASSWORD" | kb auth reset-admin --password-stdin --yes',
|
|
113847
|
+
"kb auth reset-admin --email admin@example.com --tenant acme --generate --yes --json"
|
|
113848
|
+
],
|
|
113849
|
+
flags: {
|
|
113850
|
+
email: { type: "string", description: "Admin email (default: GATEWAY_BOOTSTRAP_ADMIN_EMAIL or gateway.auth.bootstrap.adminEmail)" },
|
|
113851
|
+
tenant: { type: "string", description: `Tenant id (default: gateway.auth.bootstrap.tenantId, then GATEWAY_BOOTSTRAP_TENANT_ID, else ${DEFAULT_TENANT} \u2014 the gateway's own order)` },
|
|
113852
|
+
"password-stdin": { type: "boolean", description: "Read the new password from stdin" },
|
|
113853
|
+
generate: { type: "boolean", description: "Generate a random password and print it once" },
|
|
113854
|
+
yes: { type: "boolean", description: "Apply the changes (without it this is a dry run)" },
|
|
113855
|
+
force: { type: "boolean", description: "Run even if a gateway is listening on the configured port" },
|
|
113856
|
+
json: { type: "boolean", description: "Output in JSON format" }
|
|
113857
|
+
},
|
|
113858
|
+
async handler(ctx, _argv, flags) {
|
|
113859
|
+
const fail2 = (msg) => {
|
|
113860
|
+
if (flags.json) {
|
|
113861
|
+
ctx.ui?.json({ ok: false, error: msg });
|
|
113862
|
+
} else {
|
|
113863
|
+
ctx.ui?.error?.(msg);
|
|
113864
|
+
}
|
|
113865
|
+
return { ok: false, error: msg };
|
|
113866
|
+
};
|
|
113867
|
+
if (flags["password-stdin"] && flags.generate) {
|
|
113868
|
+
return fail2("Use either --password-stdin or --generate, not both.");
|
|
113869
|
+
}
|
|
113870
|
+
if (flags.yes && !flags["password-stdin"] && !flags.generate) {
|
|
113871
|
+
return fail2("A password source is required to apply: pass --password-stdin or --generate.");
|
|
113872
|
+
}
|
|
113873
|
+
const loaded = await loadPlatformConfig({ startDir: getContextCwd(ctx), loadEnvFile: true });
|
|
113874
|
+
const gw = gatewaySlice(loaded.effectiveConfig ?? loaded.rawConfig ?? loaded.rawPlatformConfig);
|
|
113875
|
+
const email3 = canonicalizeEmail(
|
|
113876
|
+
flags.email ?? process.env.GATEWAY_BOOTSTRAP_ADMIN_EMAIL ?? gw.auth?.bootstrap?.adminEmail ?? ""
|
|
113877
|
+
);
|
|
113878
|
+
if (!email3) {
|
|
113879
|
+
return fail2("Admin email is unknown: pass --email, or set GATEWAY_BOOTSTRAP_ADMIN_EMAIL / gateway.auth.bootstrap.adminEmail.");
|
|
113880
|
+
}
|
|
113881
|
+
const tenantId = flags.tenant ?? gw.auth?.bootstrap?.tenantId ?? process.env.GATEWAY_BOOTSTRAP_TENANT_ID ?? DEFAULT_TENANT;
|
|
113882
|
+
if (!platform.isReal("documentDatabase")) {
|
|
113883
|
+
return fail2(
|
|
113884
|
+
"No persistent documentDatabase is configured for this project, so there is no admin account on disk to recover. Check `adapters.documentDatabase` in .kb/kb.config.json."
|
|
113885
|
+
);
|
|
113886
|
+
}
|
|
113887
|
+
if (!flags.force) {
|
|
113888
|
+
const host = gw.host ?? DEFAULT_GATEWAY_HOST;
|
|
113889
|
+
const port = gw.port ?? DEFAULT_GATEWAY_PORT;
|
|
113890
|
+
if (await isPortListening(host, port)) {
|
|
113891
|
+
return fail2(
|
|
113892
|
+
`A gateway appears to be running on ${host}:${port}. Stop it first (kb-dev stop gateway) so the database is not written concurrently, or pass --force if that is not the gateway.`
|
|
113893
|
+
);
|
|
113894
|
+
}
|
|
113895
|
+
}
|
|
113896
|
+
const docs = platform.documentDatabase;
|
|
113897
|
+
const users = new UsersStore(docs);
|
|
113898
|
+
const credentials = new CredentialsStore(docs);
|
|
113899
|
+
const memberships = new MembershipsStore(docs);
|
|
113900
|
+
const sessions = new SessionsStore(docs, {
|
|
113901
|
+
refreshTtlMs: 0,
|
|
113902
|
+
graceWindowMs: 0
|
|
113903
|
+
});
|
|
113904
|
+
const existing = await users.findByEmailTenant(email3, tenantId);
|
|
113905
|
+
if (!flags.yes) {
|
|
113906
|
+
const repairs = await planRepairs(existing, tenantId, memberships);
|
|
113907
|
+
const dry = {
|
|
113908
|
+
ok: true,
|
|
113909
|
+
dryRun: true,
|
|
113910
|
+
email: email3,
|
|
113911
|
+
tenantId,
|
|
113912
|
+
userId: existing?.userId,
|
|
113913
|
+
outcome: existing ? "reset" : "created",
|
|
113914
|
+
repairs
|
|
113915
|
+
};
|
|
113916
|
+
if (flags.json) {
|
|
113917
|
+
ctx.ui?.json(dry);
|
|
113918
|
+
} else {
|
|
113919
|
+
printDryRun(ctx, existing, dry);
|
|
113920
|
+
}
|
|
113921
|
+
return dry;
|
|
113922
|
+
}
|
|
113923
|
+
const generated = flags.generate ? crypto4.randomBytes(18).toString("base64url") : void 0;
|
|
113924
|
+
const password = generated ?? await readStdin();
|
|
113925
|
+
if (!password) {
|
|
113926
|
+
return fail2("Empty password on stdin.");
|
|
113927
|
+
}
|
|
113928
|
+
const policy = gw.auth?.passwordPolicy;
|
|
113929
|
+
const bcryptCost = gw.auth?.bcryptCost ?? 12;
|
|
113930
|
+
try {
|
|
113931
|
+
const result = await resetAdmin({
|
|
113932
|
+
users,
|
|
113933
|
+
credentials,
|
|
113934
|
+
memberships,
|
|
113935
|
+
sessions,
|
|
113936
|
+
passwordPolicy: createPasswordPolicy({
|
|
113937
|
+
minLength: policy?.minLength ?? 8,
|
|
113938
|
+
maxLength: policy?.maxLength ?? 256,
|
|
113939
|
+
hibpEnabled: policy?.hibpEnabled ?? true
|
|
113940
|
+
}),
|
|
113941
|
+
bcryptCost,
|
|
113942
|
+
tenantId,
|
|
113943
|
+
email: email3,
|
|
113944
|
+
password,
|
|
113945
|
+
logger: {
|
|
113946
|
+
info: (...args) => platform.logger.info(String(args[0]), isRecord3(args[1]) ? args[1] : void 0),
|
|
113947
|
+
warn: (...args) => platform.logger.warn(String(args[0]), isRecord3(args[1]) ? args[1] : void 0)
|
|
113948
|
+
}
|
|
113949
|
+
});
|
|
113950
|
+
const out = {
|
|
113951
|
+
ok: true,
|
|
113952
|
+
dryRun: false,
|
|
113953
|
+
email: result.email,
|
|
113954
|
+
tenantId: result.tenantId,
|
|
113955
|
+
userId: result.userId,
|
|
113956
|
+
outcome: result.outcome,
|
|
113957
|
+
repairs: result.repairs,
|
|
113958
|
+
...generated ? { generatedPassword: generated } : {}
|
|
113959
|
+
};
|
|
113960
|
+
if (flags.json) {
|
|
113961
|
+
ctx.ui?.json(out);
|
|
113962
|
+
} else {
|
|
113963
|
+
printApplied(ctx, out);
|
|
113964
|
+
}
|
|
113965
|
+
return out;
|
|
113966
|
+
} catch (err) {
|
|
113967
|
+
if (err instanceof ResetAdminError) {
|
|
113968
|
+
return fail2(err.message);
|
|
113969
|
+
}
|
|
113970
|
+
throw err;
|
|
113971
|
+
}
|
|
113972
|
+
}
|
|
113973
|
+
});
|
|
113179
113974
|
function isMode(value) {
|
|
113180
113975
|
return value === "validate" || value === "reconcile" || value === "auto";
|
|
113181
113976
|
}
|