@nxgt / janus-mongo
的MongoDB适配器 @nxgt/janus:其身份存储-
用户、会话和一次性令牌——的关系存储
@nxgt/janus/permissions,以及在一个数据库上的可选API密钥存储
@nxgt/mongo.
两者都通过了 @nxgt/janus/conformance 套房对一个真正的mongod,停电
包括在内。 createMongoAdapter(db) 给出双方的名字 janus()
拿起它们,一个人把它们散开;每个也可以单独使用,如
@nxgt/janus: createMongoStores 的身份, createMongoRelations 用于
权限。
import { janus, scryptHasher } from '@nxgt/janus';
import { permissions } from '@nxgt/janus/permissions';
import { createMongoAdapter, syncMongoAdapter } from '@nxgt/janus-mongo';
await syncMongoAdapter(db); // a deployment step: creates the collections and indexes (API keys: syncMongoApiKeys, apart)
const mongo = createMongoAdapter(db); // { store, relations }
export const auth = janus({
user: User,
password: { login: 'email' },
hasher: scryptHasher(),
...mongo, // deleting a user deletes their tuples too
});
export const access = permissions({ model, store: mongo.relations });
安装
bun add @nxgt/janus-mongo @nxgt/janus @nxgt/mongo mongodb zod
bun add -d typescript
每个peer都是必需的: @nxgt/janus, @nxgt/mongo (>=0.17.0 <1), mongodb
(7), zod (4.6.5或更高版本,其中 @nxgt/mongo 用)和定义集合 typescript
(6或7)。 @nxgt/janus 是一个 同行这个包定义了no
错误类并抛出对等方自己的类,所以 instanceof StoreFailure 适用于
你的代码。喜欢它,这个包期望 "moduleResolution": "bundler":
声明导入时不需要扩展,所以 nodenext 不支持。
这个 @nxgt/mongo 地板是经过测试的,而不是声明的:包的规格和它的
运行类型检查 @nxgt/mongo 在每个CI上的Floors作业中也是0.17.0
运行。
API
| 出口 | 它是什么? |
|---|---|
createMongoAdapter(db) | { store, relations }:下面两个,键值为 janus() 带走他们,所以 janus({ …, ...mongo }) 电线。没有任何连接。 |
syncMongoAdapter(db, options?) | syncMongoStores 和 syncMongoRelations 在一个步骤:四个集合。它不会创造 apiKeys. syncAll(db) 的 @nxgt/mongo 创建 五个 (users, sessions, tokens, relations 和 apiKeys),因为导入包时会注册所有五个定义。 |
MongoAdapter | 什么类型 createMongoAdapter 的答案。 |
createMongoStores(db) | 这个 { users, sessions, tokens } 那 janus() 需要为 store. 没有任何连接。实现可选的 users.findUsers,所以 auth.<type>.findMany(ids) 是一个 $in 查询。 |
syncMongoStores(db, options?) | 创建三个集合、它们的验证器和索引,并回答它所改变的内容。需要 dbAdmin;在部署时运行它,而不是每个请求都运行它。 |
users, sessions, tokens | 这个 @nxgt/mongo 定义。定义它们注册它们 syncAll(db) 将它们与您自己的集合同步。 |
janusCollections | 这三个定义,按同步顺序排列。 |
createMongoRelations(db) | 这个 RelationStore 那 permissions() 需要为 store, janus() 作为 relations. 没有任何连接。 |
syncMongoRelations(db, options?) | 创建 relations 集合及其索引。另一个步骤:单独使用标识的应用程序不保留元组。 |
relations | 它的 @nxgt/mongo 定义。 |
createMongoApiKeys(db) | 这个 ApiKeyStore 的 @nxgt/janus'可选API密钥端口。选择加入,所以它不是 createMongoAdapter. 没有任何连接。 |
syncMongoApiKeys(db, options?) | 创建 apiKeys 集合及其索引。一个单独的步骤,比如 syncMongoRelations: syncMongoAdapter 不运行它。 |
apiKeys | 它的 @nxgt/mongo 定义。导入包注册它,所以 syncAll(db) 创建它。 |
数据库保存了什么
港口的记录,id在 _id 没有其他东西被重新命名或
编码。在shell中读取的文档读起来就像代码中的记录。
| 集合 | 索引 |
|---|---|
users | loginUnique 在 { type, logins }唯一-登录是唯一的 每个用户类型 · typeId 在 { type, _id } 对于清单 |
sessions | tokenHashUnique · userId · expiry, TTL索引 |
tokens | _id 令牌的哈希值是· userId · expiry, TTL索引 |
relations | _id 是元组, { object: { type, id }, relation, subject: { type, id, relation? } } -结构独特· objectRelation 向前一跳 subjectObjects 用于 findObjects,它提供的服务没有内存排序(测量:为10个页面检查11个键)。 |
apiKeys | _id 密钥的id是· userId 在 { userId, _id },用于列出用户的键 expiry一个TTL索引,每30天删除一个键 后 expiresAt (grace将失效的键保留为“过期”列表) |
没有秘密存储:会话和令牌保留 sha256 这个秘密,
密码是自描述散列,也是用户的散列 secondFactor.secret 一个你觉得
秘密 @nxgt/janus 已经用你的申请钥匙封好了吗
v1.<key id>.… -存储以字节为单位保存。它的
secondFactor.recoveryCodes 恢复代码的关键散列,从来没有
代码,保持有序;没有字段的因子读为没有。
countAttempt 对代码的尝试计数为一次 findOneAndUpdate 与
$inc,因此,同时进行的20次尝试回答了20种不同的计数。
spendUserTokens 是一个 updateMany 在 { userId, kind, spentAt: null } - - - - - -
和 _id: { $ne: except } 当 except 给出-
通过索引 deleteUserTokens 已经读取:它花费的每个令牌是
在一个步骤中完成匹配和书写,所以这是一种快速的救赎,而不是两者兼而有之
花同样的钱。
陷阱
-
阅读原文。
@nxgt/janus看每一个 在它完成之前写:一个登录重新读取用户以捕获一个 在运行时写入密码。一个db谁的客户机从辅助服务器读取数据 (readPreference: 'secondaryPreferred')错过了,写,和 登录成功。把适配器递给我db这是从主键中读出的 驱动程序的默认值:import { MongoClient } from 'mongodb'; const client = new MongoClient(url); // readPreference 'primary' by default const adapter = createMongoAdapter(client.db('app')); -
升级到0.3:在部署之前运行同步。 没有文件 重写-用户没有
secondFactor读取为none,这是一个标记 没有codeHash和attempts作为null和0-但是验证器 先前的同步写入拒绝新字段。直到syncMongoAdapter(db)有 运行,每次注册和一次性令牌都会失败STORE_FAILED, 引起的Document failed validation. 升级 有台阶。 -
升级到0.5:再次在部署之前运行同步。 没有文件 重写-第二个因素没有
recoveryCodes读起来好像没有- 但是前一个同步写入的验证器拒绝新字段。直到syncMongoAdapter(db)已经跑了,每次写第二个因素——enroll,activate,一个确认的代码-失败STORE_FAILED,由…引起Document failed validation. 并在用户等待之前完成rollout 恢复代码:仍处于0.4状态的实例将未写入的因子写回来 当它接受代码时。 -
升级到0.6:再次在部署前运行同步。 这个 令牌类型
stepUp是新的,又是kind枚举上一个同步写入的内容 拒绝:直到syncMongoAdapter(db)跑了吗,每一个升级请求 失败与STORE_FAILED,由…引起Document failed validation. 否 文档被重写。 -
升级到0.7:出于同样的原因,再次进行同步。 象征性的
magicLink- - - - - -@nxgt/janus0.15的登录链接-是新的:直到同步 跑过了,每一次magicLink.request失败与STORE_FAILED,由…引起Document failed validation. 不重写文档。 -
升级到0.9:出于同样的原因,再次进行同步。 象征性的
changeEmail- - - - - -@nxgt/janus0.23的链接到一个新的地址-是新的:直到 同步已运行,此类令牌的第一次写入失败STORE_FAILED,由…引起Document failed validation. 没有文件 重写。 -
升级到0.10:在部署之前运行同步。 这个
avatar槽,@nxgt/janus0.29的-是新的,严格的验证器之前的同步 写了拒绝它:直到syncMongoAdapter(db)跑了,只写那一套 化身失败,与STORE_FAILED,由…引起Document failed validation. 注册和其他每一个写通过,没有文件重写。 同步指南 请听详细报道。 -
TTL索引是存储卫生,而不是过期。 MongoDB的TTL监视器 每60秒运行一次,因此失效的会话最多可以在60秒内保持可读 分钟。核心比较
expiresAt每读一次,就会过期 它。 -
auth.collectExpired()答案UNSUPPORTED用这个适配器。的TTL 索引已经删除了失效的会话和令牌,所以deleteExpiredSessions和deleteExpiredTokens都是故意不实现的。 -
没有为您调用同步函数 - - - - - -
syncMongoAdapter,或者syncMongoStores对于恒等式。核心从不管理模式。 没有它,就没有唯一索引,也没有什么可以阻止两个并发注册 只需一次登录。 -
在登录索引以外的索引上的重复键是
StoreFailure,而不是LOGIN_TAKEN. 这样的重复是一个适配器错误,并且 将其报告为被占用的登录将告诉某人他们的电子邮件正在使用 事实并非如此。 -
一个以上元组的关系写入是一个事务, MongoDB运行 仅在副本集上执行事务。
grant和revoke写一个元组和 到处运行;在独立的mongod上,多元写入失败STORE_FAILED. 交易不会重试:是司机的withTransaction在回答之前,将重试停机两分钟,并且写入是 幂等的,所以自己再试一次。 -
findObjects订单id作为MongoDB,通过UTF-8字节。list()排序 它自己收集,所以它的页面不依赖于它;一个打电话的人 store直接看到字节顺序,这与JavaScript不同<只有过去 U +飞行符。
文档
类型安全,计数
编译器拒绝了11个貌似合理的错误,每个错误都有一个
@ts-expect-error 在 test/types/adapter.ts:
- 连接字符串而不是数据库;
@nxgt/mongo的连接,而不是它的db,createMongoAdapter;- 司机的
MongoClient而不是它的一个数据库; @nxgt/mongo的连接,而不是它的db,syncMongoAdapter;- 同样的,
syncMongoRelations; - 承诺
connectMongo没有等待的答案; - 错误情况下的同步选项:
dryrun用于dryRun; - 整个适配器为
janus()的store,而不是传播; - 关系存储为
janus()的store; - 整个适配器为
permissions()的store,而不是mongo.relations; - 标识存储为
permissions()的store.
执照
麻省理工学院