@shware/security 6.2.2 → 6.3.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/README.md CHANGED
@@ -226,10 +226,14 @@ type SessionEntity = Parameters<DBAdapter['save']>[0];
226
226
 
227
227
  class SessionAdapter implements DBAdapter {
228
228
  async save(entity: SessionEntity) {
229
- await db
230
- .insert(session)
231
- .values(toRow(entity))
232
- .onConflictDoUpdate({ target: session.sessionId, set: toRow(entity) });
229
+ // `isNew` says whether this is the first write: a session created in this request is
230
+ // inserted, one that was loaded is only ever updated. Updating zero rows means a concurrent
231
+ // logout or kick deleted it, and an upsert there would bring the session back from the dead.
232
+ if (entity.isNew) {
233
+ await db.insert(session).values(toRow(entity));
234
+ return;
235
+ }
236
+ await db.update(session).set(toRow(entity)).where(eq(session.sessionId, entity.sessionId));
233
237
  }
234
238
  async deleteById(sessionId: string) {
235
239
  await db.delete(session).where(eq(session.sessionId, sessionId));
@@ -28,6 +28,7 @@ var DBIndexedSessionRepository = class {
28
28
  return {
29
29
  primaryId: session.primaryId,
30
30
  sessionId: session.getId(),
31
+ isNew: session.isNew,
31
32
  creationTime: session.getCreationTime(),
32
33
  lastAccessTime: session.getLastAccessedTime(),
33
34
  maxInactiveInterval: session.getMaxInactiveInterval(),
@@ -1 +1 @@
1
- {"version":3,"file":"db.cjs","names":["MapSession","randomUUID","resolveIndexesFor","PRINCIPAL_NAME_INDEX_NAME"],"sources":["../../src/session/db.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PRINCIPAL_NAME_INDEX_NAME, resolveIndexesFor } from './common';\nimport { MapSession } from './map-session';\nimport type { SessionRepository } from './types';\n\ninterface SessionEntity {\n primaryId: string;\n sessionId: string;\n creationTime: number;\n lastAccessTime: number;\n maxInactiveInterval: number;\n expiryTime: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n attributes: Record<string, any>;\n principalName?: string | null;\n}\n\nclass DBSession extends MapSession {\n public isNew: boolean;\n public primaryId: string;\n\n constructor(cached: MapSession, isNew: boolean) {\n super(cached);\n this.isNew = isNew;\n this.primaryId = randomUUID();\n }\n}\n\nexport interface DBAdapter {\n save(session: SessionEntity): Promise<void>;\n deleteById(sessionId: string): Promise<void>;\n findById(sessionId: string): Promise<SessionEntity | null>;\n findByPrincipalName(principalName: string): Promise<SessionEntity[]>;\n cleanupExpiredSessions(cleanupCount?: number): Promise<void>;\n}\n\nexport class DBIndexedSessionRepository implements SessionRepository<DBSession> {\n private defaultMaxInactiveInterval = 1800;\n private readonly db: DBAdapter;\n\n constructor(db: DBAdapter) {\n this.db = db;\n }\n\n setDefaultMaxInactiveInterval(interval: number) {\n this.defaultMaxInactiveInterval = interval;\n }\n\n private mapSessionToEntity(session: DBSession): SessionEntity {\n const attributes: Record<string, string | number | null> = {};\n for (const name of session.getAttributeNames()) {\n attributes[name] = session.getAttribute(name);\n }\n const principalName = resolveIndexesFor(session);\n\n return {\n primaryId: session.primaryId,\n sessionId: session.getId(),\n creationTime: session.getCreationTime(),\n lastAccessTime: session.getLastAccessedTime(),\n maxInactiveInterval: session.getMaxInactiveInterval(),\n expiryTime: session.getLastAccessedTime() + session.getMaxInactiveInterval() * 1000,\n attributes,\n principalName,\n };\n }\n\n private mapEntityToSession(entity: SessionEntity, allowExpired: boolean): DBSession | null {\n const loaded = new MapSession(entity.sessionId);\n loaded.setCreationTime(entity.creationTime);\n loaded.setLastAccessedTime(entity.lastAccessTime);\n loaded.setMaxInactiveInterval(entity.maxInactiveInterval);\n for (const [key, value] of Object.entries(entity.attributes)) {\n loaded.setAttribute(key, value);\n }\n\n if (!allowExpired && loaded.isExpired()) return null;\n const session = new DBSession(loaded, false);\n session.primaryId = entity.primaryId;\n return session;\n }\n\n private async getSession(id: string, allowExpired: boolean): Promise<DBSession | null> {\n const entity = await this.db.findById(id);\n if (!entity) return null;\n return this.mapEntityToSession(entity, allowExpired);\n }\n\n // override\n createSession() {\n const cached = new MapSession();\n cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);\n return new DBSession(cached, true);\n }\n\n async save(session: DBSession) {\n const entity = this.mapSessionToEntity(session);\n await this.db.save(entity);\n session.isNew = false;\n }\n\n async findById(sessionId: string): Promise<DBSession | null> {\n return this.getSession(sessionId, false);\n }\n\n async deleteById(sessionId: string) {\n await this.db.deleteById(sessionId);\n }\n\n async findByIndexNameAndIndexValue(\n indexName: string,\n indexValue: string\n ): Promise<Map<string, DBSession>> {\n if (indexName !== PRINCIPAL_NAME_INDEX_NAME) {\n return new Map<string, DBSession>();\n }\n const entities = await this.db.findByPrincipalName(indexValue);\n if (entities.length === 0) {\n return new Map<string, DBSession>();\n }\n const sessions = new Map<string, DBSession>();\n for (const entity of entities) {\n const session = this.mapEntityToSession(entity, false);\n if (session) {\n sessions.set(session.getId(), session);\n }\n }\n return sessions;\n }\n\n async findByPrincipalName(principalName: string): Promise<Map<string, DBSession>> {\n return this.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);\n }\n\n async cleanupExpiredSessions(cleanupCount?: number): Promise<void> {\n return this.db.cleanupExpiredSessions(cleanupCount);\n }\n}\n"],"mappings":";;;;;AAiBA,IAAM,YAAN,cAAwBA,4BAAAA,WAAW;CACjC;CACA;CAEA,YAAY,QAAoB,OAAgB;EAC9C,MAAM,MAAM;EACZ,KAAK,QAAQ;EACb,KAAK,aAAA,GAAYC,YAAAA,WAAAA,CAAW;CAC9B;AACF;AAUA,IAAa,6BAAb,MAAgF;CAC9E,6BAAqC;CACrC;CAEA,YAAY,IAAe;EACzB,KAAK,KAAK;CACZ;CAEA,8BAA8B,UAAkB;EAC9C,KAAK,6BAA6B;CACpC;CAEA,mBAA2B,SAAmC;EAC5D,MAAM,aAAqD,CAAC;EAC5D,KAAK,MAAM,QAAQ,QAAQ,kBAAkB,GAC3C,WAAW,QAAQ,QAAQ,aAAa,IAAI;EAE9C,MAAM,gBAAgBC,uBAAAA,kBAAkB,OAAO;EAE/C,OAAO;GACL,WAAW,QAAQ;GACnB,WAAW,QAAQ,MAAM;GACzB,cAAc,QAAQ,gBAAgB;GACtC,gBAAgB,QAAQ,oBAAoB;GAC5C,qBAAqB,QAAQ,uBAAuB;GACpD,YAAY,QAAQ,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;GAC/E;GACA;EACF;CACF;CAEA,mBAA2B,QAAuB,cAAyC;EACzF,MAAM,SAAS,IAAIF,4BAAAA,WAAW,OAAO,SAAS;EAC9C,OAAO,gBAAgB,OAAO,YAAY;EAC1C,OAAO,oBAAoB,OAAO,cAAc;EAChD,OAAO,uBAAuB,OAAO,mBAAmB;EACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,UAAU,GACzD,OAAO,aAAa,KAAK,KAAK;EAGhC,IAAI,CAAC,gBAAgB,OAAO,UAAU,GAAG,OAAO;EAChD,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK;EAC3C,QAAQ,YAAY,OAAO;EAC3B,OAAO;CACT;CAEA,MAAc,WAAW,IAAY,cAAkD;EACrF,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,EAAE;EACxC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,KAAK,mBAAmB,QAAQ,YAAY;CACrD;CAGA,gBAAgB;EACd,MAAM,SAAS,IAAIA,4BAAAA,WAAW;EAC9B,OAAO,uBAAuB,KAAK,0BAA0B;EAC7D,OAAO,IAAI,UAAU,QAAQ,IAAI;CACnC;CAEA,MAAM,KAAK,SAAoB;EAC7B,MAAM,SAAS,KAAK,mBAAmB,OAAO;EAC9C,MAAM,KAAK,GAAG,KAAK,MAAM;EACzB,QAAQ,QAAQ;CAClB;CAEA,MAAM,SAAS,WAA8C;EAC3D,OAAO,KAAK,WAAW,WAAW,KAAK;CACzC;CAEA,MAAM,WAAW,WAAmB;EAClC,MAAM,KAAK,GAAG,WAAW,SAAS;CACpC;CAEA,MAAM,6BACJ,WACA,YACiC;EACjC,IAAI,cAAA,0FACF,uBAAO,IAAI,IAAuB;EAEpC,MAAM,WAAW,MAAM,KAAK,GAAG,oBAAoB,UAAU;EAC7D,IAAI,SAAS,WAAW,GACtB,uBAAO,IAAI,IAAuB;EAEpC,MAAM,2BAAW,IAAI,IAAuB;EAC5C,KAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,UAAU,KAAK,mBAAmB,QAAQ,KAAK;GACrD,IAAI,SACF,SAAS,IAAI,QAAQ,MAAM,GAAG,OAAO;EAEzC;EACA,OAAO;CACT;CAEA,MAAM,oBAAoB,eAAwD;EAChF,OAAO,KAAK,6BAA6BG,uBAAAA,2BAA2B,aAAa;CACnF;CAEA,MAAM,uBAAuB,cAAsC;EACjE,OAAO,KAAK,GAAG,uBAAuB,YAAY;CACpD;AACF"}
1
+ {"version":3,"file":"db.cjs","names":["MapSession","randomUUID","resolveIndexesFor","PRINCIPAL_NAME_INDEX_NAME"],"sources":["../../src/session/db.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PRINCIPAL_NAME_INDEX_NAME, resolveIndexesFor } from './common';\nimport { MapSession } from './map-session';\nimport type { SessionRepository } from './types';\n\ninterface SessionEntity {\n primaryId: string;\n sessionId: string;\n /**\n * Set on the entity `save` hands the adapter: `true` for a session created in this request and\n * never stored, `false` for one the adapter returned from `findById` / `findByPrincipalName`. As\n * in Spring Session's `JdbcIndexedSessionRepository`, it is what decides between an INSERT and\n * an UPDATE — an UPDATE that touches no row then means the session was deleted meanwhile\n * (logout, kick) and must not be written back. Absent on entities the adapter returns.\n */\n isNew?: boolean;\n creationTime: number;\n lastAccessTime: number;\n maxInactiveInterval: number;\n expiryTime: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n attributes: Record<string, any>;\n principalName?: string | null;\n}\n\nclass DBSession extends MapSession {\n public isNew: boolean;\n public primaryId: string;\n\n constructor(cached: MapSession, isNew: boolean) {\n super(cached);\n this.isNew = isNew;\n this.primaryId = randomUUID();\n }\n}\n\nexport interface DBAdapter {\n save(session: SessionEntity): Promise<void>;\n deleteById(sessionId: string): Promise<void>;\n findById(sessionId: string): Promise<SessionEntity | null>;\n findByPrincipalName(principalName: string): Promise<SessionEntity[]>;\n cleanupExpiredSessions(cleanupCount?: number): Promise<void>;\n}\n\nexport class DBIndexedSessionRepository implements SessionRepository<DBSession> {\n private defaultMaxInactiveInterval = 1800;\n private readonly db: DBAdapter;\n\n constructor(db: DBAdapter) {\n this.db = db;\n }\n\n setDefaultMaxInactiveInterval(interval: number) {\n this.defaultMaxInactiveInterval = interval;\n }\n\n private mapSessionToEntity(session: DBSession): SessionEntity {\n const attributes: Record<string, string | number | null> = {};\n for (const name of session.getAttributeNames()) {\n attributes[name] = session.getAttribute(name);\n }\n const principalName = resolveIndexesFor(session);\n\n return {\n primaryId: session.primaryId,\n sessionId: session.getId(),\n isNew: session.isNew,\n creationTime: session.getCreationTime(),\n lastAccessTime: session.getLastAccessedTime(),\n maxInactiveInterval: session.getMaxInactiveInterval(),\n expiryTime: session.getLastAccessedTime() + session.getMaxInactiveInterval() * 1000,\n attributes,\n principalName,\n };\n }\n\n private mapEntityToSession(entity: SessionEntity, allowExpired: boolean): DBSession | null {\n const loaded = new MapSession(entity.sessionId);\n loaded.setCreationTime(entity.creationTime);\n loaded.setLastAccessedTime(entity.lastAccessTime);\n loaded.setMaxInactiveInterval(entity.maxInactiveInterval);\n for (const [key, value] of Object.entries(entity.attributes)) {\n loaded.setAttribute(key, value);\n }\n\n if (!allowExpired && loaded.isExpired()) return null;\n const session = new DBSession(loaded, false);\n session.primaryId = entity.primaryId;\n return session;\n }\n\n private async getSession(id: string, allowExpired: boolean): Promise<DBSession | null> {\n const entity = await this.db.findById(id);\n if (!entity) return null;\n return this.mapEntityToSession(entity, allowExpired);\n }\n\n // override\n createSession() {\n const cached = new MapSession();\n cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);\n return new DBSession(cached, true);\n }\n\n async save(session: DBSession) {\n const entity = this.mapSessionToEntity(session);\n await this.db.save(entity);\n session.isNew = false;\n }\n\n async findById(sessionId: string): Promise<DBSession | null> {\n return this.getSession(sessionId, false);\n }\n\n async deleteById(sessionId: string) {\n await this.db.deleteById(sessionId);\n }\n\n async findByIndexNameAndIndexValue(\n indexName: string,\n indexValue: string\n ): Promise<Map<string, DBSession>> {\n if (indexName !== PRINCIPAL_NAME_INDEX_NAME) {\n return new Map<string, DBSession>();\n }\n const entities = await this.db.findByPrincipalName(indexValue);\n if (entities.length === 0) {\n return new Map<string, DBSession>();\n }\n const sessions = new Map<string, DBSession>();\n for (const entity of entities) {\n const session = this.mapEntityToSession(entity, false);\n if (session) {\n sessions.set(session.getId(), session);\n }\n }\n return sessions;\n }\n\n async findByPrincipalName(principalName: string): Promise<Map<string, DBSession>> {\n return this.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);\n }\n\n async cleanupExpiredSessions(cleanupCount?: number): Promise<void> {\n return this.db.cleanupExpiredSessions(cleanupCount);\n }\n}\n"],"mappings":";;;;;AAyBA,IAAM,YAAN,cAAwBA,4BAAAA,WAAW;CACjC;CACA;CAEA,YAAY,QAAoB,OAAgB;EAC9C,MAAM,MAAM;EACZ,KAAK,QAAQ;EACb,KAAK,aAAA,GAAYC,YAAAA,WAAAA,CAAW;CAC9B;AACF;AAUA,IAAa,6BAAb,MAAgF;CAC9E,6BAAqC;CACrC;CAEA,YAAY,IAAe;EACzB,KAAK,KAAK;CACZ;CAEA,8BAA8B,UAAkB;EAC9C,KAAK,6BAA6B;CACpC;CAEA,mBAA2B,SAAmC;EAC5D,MAAM,aAAqD,CAAC;EAC5D,KAAK,MAAM,QAAQ,QAAQ,kBAAkB,GAC3C,WAAW,QAAQ,QAAQ,aAAa,IAAI;EAE9C,MAAM,gBAAgBC,uBAAAA,kBAAkB,OAAO;EAE/C,OAAO;GACL,WAAW,QAAQ;GACnB,WAAW,QAAQ,MAAM;GACzB,OAAO,QAAQ;GACf,cAAc,QAAQ,gBAAgB;GACtC,gBAAgB,QAAQ,oBAAoB;GAC5C,qBAAqB,QAAQ,uBAAuB;GACpD,YAAY,QAAQ,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;GAC/E;GACA;EACF;CACF;CAEA,mBAA2B,QAAuB,cAAyC;EACzF,MAAM,SAAS,IAAIF,4BAAAA,WAAW,OAAO,SAAS;EAC9C,OAAO,gBAAgB,OAAO,YAAY;EAC1C,OAAO,oBAAoB,OAAO,cAAc;EAChD,OAAO,uBAAuB,OAAO,mBAAmB;EACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,UAAU,GACzD,OAAO,aAAa,KAAK,KAAK;EAGhC,IAAI,CAAC,gBAAgB,OAAO,UAAU,GAAG,OAAO;EAChD,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK;EAC3C,QAAQ,YAAY,OAAO;EAC3B,OAAO;CACT;CAEA,MAAc,WAAW,IAAY,cAAkD;EACrF,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,EAAE;EACxC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,KAAK,mBAAmB,QAAQ,YAAY;CACrD;CAGA,gBAAgB;EACd,MAAM,SAAS,IAAIA,4BAAAA,WAAW;EAC9B,OAAO,uBAAuB,KAAK,0BAA0B;EAC7D,OAAO,IAAI,UAAU,QAAQ,IAAI;CACnC;CAEA,MAAM,KAAK,SAAoB;EAC7B,MAAM,SAAS,KAAK,mBAAmB,OAAO;EAC9C,MAAM,KAAK,GAAG,KAAK,MAAM;EACzB,QAAQ,QAAQ;CAClB;CAEA,MAAM,SAAS,WAA8C;EAC3D,OAAO,KAAK,WAAW,WAAW,KAAK;CACzC;CAEA,MAAM,WAAW,WAAmB;EAClC,MAAM,KAAK,GAAG,WAAW,SAAS;CACpC;CAEA,MAAM,6BACJ,WACA,YACiC;EACjC,IAAI,cAAA,0FACF,uBAAO,IAAI,IAAuB;EAEpC,MAAM,WAAW,MAAM,KAAK,GAAG,oBAAoB,UAAU;EAC7D,IAAI,SAAS,WAAW,GACtB,uBAAO,IAAI,IAAuB;EAEpC,MAAM,2BAAW,IAAI,IAAuB;EAC5C,KAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,UAAU,KAAK,mBAAmB,QAAQ,KAAK;GACrD,IAAI,SACF,SAAS,IAAI,QAAQ,MAAM,GAAG,OAAO;EAEzC;EACA,OAAO;CACT;CAEA,MAAM,oBAAoB,eAAwD;EAChF,OAAO,KAAK,6BAA6BG,uBAAAA,2BAA2B,aAAa;CACnF;CAEA,MAAM,uBAAuB,cAAsC;EACjE,OAAO,KAAK,GAAG,uBAAuB,YAAY;CACpD;AACF"}
@@ -4,6 +4,14 @@ import { MapSession } from "./map-session.cjs";
4
4
  interface SessionEntity {
5
5
  primaryId: string;
6
6
  sessionId: string;
7
+ /**
8
+ * Set on the entity `save` hands the adapter: `true` for a session created in this request and
9
+ * never stored, `false` for one the adapter returned from `findById` / `findByPrincipalName`. As
10
+ * in Spring Session's `JdbcIndexedSessionRepository`, it is what decides between an INSERT and
11
+ * an UPDATE — an UPDATE that touches no row then means the session was deleted meanwhile
12
+ * (logout, kick) and must not be written back. Absent on entities the adapter returns.
13
+ */
14
+ isNew?: boolean;
7
15
  creationTime: number;
8
16
  lastAccessTime: number;
9
17
  maxInactiveInterval: number;
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.cts","names":[],"sources":["../../src/session/db.ts"],"mappings":";;;UAKU;EACR;EACA;EACA;EACA;EACA;EACA;EAEA,YAAY;EACZ;;cAGI,kBAAkB;EACf;EACA;EAEP,YAAY,QAAQ,YAAY;;iBAOjB;EACf,KAAK,SAAS,gBAAgB;EAC9B,WAAW,oBAAoB;EAC/B,SAAS,oBAAoB,QAAQ;EACrC,oBAAoB,wBAAwB,QAAQ;EACpD,uBAAuB,wBAAwB;;qBAGpC,sCAAsC,kBAAkB;UAC3D;mBACS;EAEjB,YAAY,IAAI;EAIhB,8BAA8B;UAItB;UAmBA;UAeM;EAOd,iBAAa;EAMP,KAAK,SAAS,YAAS;EAMvB,SAAS,oBAAoB,QAAQ;EAIrC,WAAW,oBAAiB;EAI5B,6BACJ,mBACA,qBACC,QAAQ,YAAY;EAkBjB,oBAAoB,wBAAwB,QAAQ,YAAY;EAIhE,uBAAuB,wBAAwB"}
1
+ {"version":3,"file":"db.d.cts","names":[],"sources":["../../src/session/db.ts"],"mappings":";;;UAKU;EACR;EACA;;;;;;;;EAQA;EACA;EACA;EACA;EACA;EAEA,YAAY;EACZ;;cAGI,kBAAkB;EACf;EACA;EAEP,YAAY,QAAQ,YAAY;;iBAOjB;EACf,KAAK,SAAS,gBAAgB;EAC9B,WAAW,oBAAoB;EAC/B,SAAS,oBAAoB,QAAQ;EACrC,oBAAoB,wBAAwB,QAAQ;EACpD,uBAAuB,wBAAwB;;qBAGpC,sCAAsC,kBAAkB;UAC3D;mBACS;EAEjB,YAAY,IAAI;EAIhB,8BAA8B;UAItB;UAoBA;UAeM;EAOd,iBAAa;EAMP,KAAK,SAAS,YAAS;EAMvB,SAAS,oBAAoB,QAAQ;EAIrC,WAAW,oBAAiB;EAI5B,6BACJ,mBACA,qBACC,QAAQ,YAAY;EAkBjB,oBAAoB,wBAAwB,QAAQ,YAAY;EAIhE,uBAAuB,wBAAwB"}
@@ -4,6 +4,14 @@ import { MapSession } from "./map-session.mjs";
4
4
  interface SessionEntity {
5
5
  primaryId: string;
6
6
  sessionId: string;
7
+ /**
8
+ * Set on the entity `save` hands the adapter: `true` for a session created in this request and
9
+ * never stored, `false` for one the adapter returned from `findById` / `findByPrincipalName`. As
10
+ * in Spring Session's `JdbcIndexedSessionRepository`, it is what decides between an INSERT and
11
+ * an UPDATE — an UPDATE that touches no row then means the session was deleted meanwhile
12
+ * (logout, kick) and must not be written back. Absent on entities the adapter returns.
13
+ */
14
+ isNew?: boolean;
7
15
  creationTime: number;
8
16
  lastAccessTime: number;
9
17
  maxInactiveInterval: number;
@@ -1 +1 @@
1
- {"version":3,"file":"db.d.mts","names":[],"sources":["../../src/session/db.ts"],"mappings":";;;UAKU;EACR;EACA;EACA;EACA;EACA;EACA;EAEA,YAAY;EACZ;;cAGI,kBAAkB;EACf;EACA;EAEP,YAAY,QAAQ,YAAY;;iBAOjB;EACf,KAAK,SAAS,gBAAgB;EAC9B,WAAW,oBAAoB;EAC/B,SAAS,oBAAoB,QAAQ;EACrC,oBAAoB,wBAAwB,QAAQ;EACpD,uBAAuB,wBAAwB;;qBAGpC,sCAAsC,kBAAkB;UAC3D;mBACS;EAEjB,YAAY,IAAI;EAIhB,8BAA8B;UAItB;UAmBA;UAeM;EAOd,iBAAa;EAMP,KAAK,SAAS,YAAS;EAMvB,SAAS,oBAAoB,QAAQ;EAIrC,WAAW,oBAAiB;EAI5B,6BACJ,mBACA,qBACC,QAAQ,YAAY;EAkBjB,oBAAoB,wBAAwB,QAAQ,YAAY;EAIhE,uBAAuB,wBAAwB"}
1
+ {"version":3,"file":"db.d.mts","names":[],"sources":["../../src/session/db.ts"],"mappings":";;;UAKU;EACR;EACA;;;;;;;;EAQA;EACA;EACA;EACA;EACA;EAEA,YAAY;EACZ;;cAGI,kBAAkB;EACf;EACA;EAEP,YAAY,QAAQ,YAAY;;iBAOjB;EACf,KAAK,SAAS,gBAAgB;EAC9B,WAAW,oBAAoB;EAC/B,SAAS,oBAAoB,QAAQ;EACrC,oBAAoB,wBAAwB,QAAQ;EACpD,uBAAuB,wBAAwB;;qBAGpC,sCAAsC,kBAAkB;UAC3D;mBACS;EAEjB,YAAY,IAAI;EAIhB,8BAA8B;UAItB;UAoBA;UAeM;EAOd,iBAAa;EAMP,KAAK,SAAS,YAAS;EAMvB,SAAS,oBAAoB,QAAQ;EAIrC,WAAW,oBAAiB;EAI5B,6BACJ,mBACA,qBACC,QAAQ,YAAY;EAkBjB,oBAAoB,wBAAwB,QAAQ,YAAY;EAIhE,uBAAuB,wBAAwB"}
@@ -27,6 +27,7 @@ var DBIndexedSessionRepository = class {
27
27
  return {
28
28
  primaryId: session.primaryId,
29
29
  sessionId: session.getId(),
30
+ isNew: session.isNew,
30
31
  creationTime: session.getCreationTime(),
31
32
  lastAccessTime: session.getLastAccessedTime(),
32
33
  maxInactiveInterval: session.getMaxInactiveInterval(),
@@ -1 +1 @@
1
- {"version":3,"file":"db.mjs","names":[],"sources":["../../src/session/db.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PRINCIPAL_NAME_INDEX_NAME, resolveIndexesFor } from './common';\nimport { MapSession } from './map-session';\nimport type { SessionRepository } from './types';\n\ninterface SessionEntity {\n primaryId: string;\n sessionId: string;\n creationTime: number;\n lastAccessTime: number;\n maxInactiveInterval: number;\n expiryTime: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n attributes: Record<string, any>;\n principalName?: string | null;\n}\n\nclass DBSession extends MapSession {\n public isNew: boolean;\n public primaryId: string;\n\n constructor(cached: MapSession, isNew: boolean) {\n super(cached);\n this.isNew = isNew;\n this.primaryId = randomUUID();\n }\n}\n\nexport interface DBAdapter {\n save(session: SessionEntity): Promise<void>;\n deleteById(sessionId: string): Promise<void>;\n findById(sessionId: string): Promise<SessionEntity | null>;\n findByPrincipalName(principalName: string): Promise<SessionEntity[]>;\n cleanupExpiredSessions(cleanupCount?: number): Promise<void>;\n}\n\nexport class DBIndexedSessionRepository implements SessionRepository<DBSession> {\n private defaultMaxInactiveInterval = 1800;\n private readonly db: DBAdapter;\n\n constructor(db: DBAdapter) {\n this.db = db;\n }\n\n setDefaultMaxInactiveInterval(interval: number) {\n this.defaultMaxInactiveInterval = interval;\n }\n\n private mapSessionToEntity(session: DBSession): SessionEntity {\n const attributes: Record<string, string | number | null> = {};\n for (const name of session.getAttributeNames()) {\n attributes[name] = session.getAttribute(name);\n }\n const principalName = resolveIndexesFor(session);\n\n return {\n primaryId: session.primaryId,\n sessionId: session.getId(),\n creationTime: session.getCreationTime(),\n lastAccessTime: session.getLastAccessedTime(),\n maxInactiveInterval: session.getMaxInactiveInterval(),\n expiryTime: session.getLastAccessedTime() + session.getMaxInactiveInterval() * 1000,\n attributes,\n principalName,\n };\n }\n\n private mapEntityToSession(entity: SessionEntity, allowExpired: boolean): DBSession | null {\n const loaded = new MapSession(entity.sessionId);\n loaded.setCreationTime(entity.creationTime);\n loaded.setLastAccessedTime(entity.lastAccessTime);\n loaded.setMaxInactiveInterval(entity.maxInactiveInterval);\n for (const [key, value] of Object.entries(entity.attributes)) {\n loaded.setAttribute(key, value);\n }\n\n if (!allowExpired && loaded.isExpired()) return null;\n const session = new DBSession(loaded, false);\n session.primaryId = entity.primaryId;\n return session;\n }\n\n private async getSession(id: string, allowExpired: boolean): Promise<DBSession | null> {\n const entity = await this.db.findById(id);\n if (!entity) return null;\n return this.mapEntityToSession(entity, allowExpired);\n }\n\n // override\n createSession() {\n const cached = new MapSession();\n cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);\n return new DBSession(cached, true);\n }\n\n async save(session: DBSession) {\n const entity = this.mapSessionToEntity(session);\n await this.db.save(entity);\n session.isNew = false;\n }\n\n async findById(sessionId: string): Promise<DBSession | null> {\n return this.getSession(sessionId, false);\n }\n\n async deleteById(sessionId: string) {\n await this.db.deleteById(sessionId);\n }\n\n async findByIndexNameAndIndexValue(\n indexName: string,\n indexValue: string\n ): Promise<Map<string, DBSession>> {\n if (indexName !== PRINCIPAL_NAME_INDEX_NAME) {\n return new Map<string, DBSession>();\n }\n const entities = await this.db.findByPrincipalName(indexValue);\n if (entities.length === 0) {\n return new Map<string, DBSession>();\n }\n const sessions = new Map<string, DBSession>();\n for (const entity of entities) {\n const session = this.mapEntityToSession(entity, false);\n if (session) {\n sessions.set(session.getId(), session);\n }\n }\n return sessions;\n }\n\n async findByPrincipalName(principalName: string): Promise<Map<string, DBSession>> {\n return this.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);\n }\n\n async cleanupExpiredSessions(cleanupCount?: number): Promise<void> {\n return this.db.cleanupExpiredSessions(cleanupCount);\n }\n}\n"],"mappings":";;;;AAiBA,IAAM,YAAN,cAAwB,WAAW;CACjC;CACA;CAEA,YAAY,QAAoB,OAAgB;EAC9C,MAAM,MAAM;EACZ,KAAK,QAAQ;EACb,KAAK,YAAY,WAAW;CAC9B;AACF;AAUA,IAAa,6BAAb,MAAgF;CAC9E,6BAAqC;CACrC;CAEA,YAAY,IAAe;EACzB,KAAK,KAAK;CACZ;CAEA,8BAA8B,UAAkB;EAC9C,KAAK,6BAA6B;CACpC;CAEA,mBAA2B,SAAmC;EAC5D,MAAM,aAAqD,CAAC;EAC5D,KAAK,MAAM,QAAQ,QAAQ,kBAAkB,GAC3C,WAAW,QAAQ,QAAQ,aAAa,IAAI;EAE9C,MAAM,gBAAgB,kBAAkB,OAAO;EAE/C,OAAO;GACL,WAAW,QAAQ;GACnB,WAAW,QAAQ,MAAM;GACzB,cAAc,QAAQ,gBAAgB;GACtC,gBAAgB,QAAQ,oBAAoB;GAC5C,qBAAqB,QAAQ,uBAAuB;GACpD,YAAY,QAAQ,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;GAC/E;GACA;EACF;CACF;CAEA,mBAA2B,QAAuB,cAAyC;EACzF,MAAM,SAAS,IAAI,WAAW,OAAO,SAAS;EAC9C,OAAO,gBAAgB,OAAO,YAAY;EAC1C,OAAO,oBAAoB,OAAO,cAAc;EAChD,OAAO,uBAAuB,OAAO,mBAAmB;EACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,UAAU,GACzD,OAAO,aAAa,KAAK,KAAK;EAGhC,IAAI,CAAC,gBAAgB,OAAO,UAAU,GAAG,OAAO;EAChD,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK;EAC3C,QAAQ,YAAY,OAAO;EAC3B,OAAO;CACT;CAEA,MAAc,WAAW,IAAY,cAAkD;EACrF,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,EAAE;EACxC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,KAAK,mBAAmB,QAAQ,YAAY;CACrD;CAGA,gBAAgB;EACd,MAAM,SAAS,IAAI,WAAW;EAC9B,OAAO,uBAAuB,KAAK,0BAA0B;EAC7D,OAAO,IAAI,UAAU,QAAQ,IAAI;CACnC;CAEA,MAAM,KAAK,SAAoB;EAC7B,MAAM,SAAS,KAAK,mBAAmB,OAAO;EAC9C,MAAM,KAAK,GAAG,KAAK,MAAM;EACzB,QAAQ,QAAQ;CAClB;CAEA,MAAM,SAAS,WAA8C;EAC3D,OAAO,KAAK,WAAW,WAAW,KAAK;CACzC;CAEA,MAAM,WAAW,WAAmB;EAClC,MAAM,KAAK,GAAG,WAAW,SAAS;CACpC;CAEA,MAAM,6BACJ,WACA,YACiC;EACjC,IAAI,cAAA,0FACF,uBAAO,IAAI,IAAuB;EAEpC,MAAM,WAAW,MAAM,KAAK,GAAG,oBAAoB,UAAU;EAC7D,IAAI,SAAS,WAAW,GACtB,uBAAO,IAAI,IAAuB;EAEpC,MAAM,2BAAW,IAAI,IAAuB;EAC5C,KAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,UAAU,KAAK,mBAAmB,QAAQ,KAAK;GACrD,IAAI,SACF,SAAS,IAAI,QAAQ,MAAM,GAAG,OAAO;EAEzC;EACA,OAAO;CACT;CAEA,MAAM,oBAAoB,eAAwD;EAChF,OAAO,KAAK,6BAA6B,2BAA2B,aAAa;CACnF;CAEA,MAAM,uBAAuB,cAAsC;EACjE,OAAO,KAAK,GAAG,uBAAuB,YAAY;CACpD;AACF"}
1
+ {"version":3,"file":"db.mjs","names":[],"sources":["../../src/session/db.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { PRINCIPAL_NAME_INDEX_NAME, resolveIndexesFor } from './common';\nimport { MapSession } from './map-session';\nimport type { SessionRepository } from './types';\n\ninterface SessionEntity {\n primaryId: string;\n sessionId: string;\n /**\n * Set on the entity `save` hands the adapter: `true` for a session created in this request and\n * never stored, `false` for one the adapter returned from `findById` / `findByPrincipalName`. As\n * in Spring Session's `JdbcIndexedSessionRepository`, it is what decides between an INSERT and\n * an UPDATE — an UPDATE that touches no row then means the session was deleted meanwhile\n * (logout, kick) and must not be written back. Absent on entities the adapter returns.\n */\n isNew?: boolean;\n creationTime: number;\n lastAccessTime: number;\n maxInactiveInterval: number;\n expiryTime: number;\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n attributes: Record<string, any>;\n principalName?: string | null;\n}\n\nclass DBSession extends MapSession {\n public isNew: boolean;\n public primaryId: string;\n\n constructor(cached: MapSession, isNew: boolean) {\n super(cached);\n this.isNew = isNew;\n this.primaryId = randomUUID();\n }\n}\n\nexport interface DBAdapter {\n save(session: SessionEntity): Promise<void>;\n deleteById(sessionId: string): Promise<void>;\n findById(sessionId: string): Promise<SessionEntity | null>;\n findByPrincipalName(principalName: string): Promise<SessionEntity[]>;\n cleanupExpiredSessions(cleanupCount?: number): Promise<void>;\n}\n\nexport class DBIndexedSessionRepository implements SessionRepository<DBSession> {\n private defaultMaxInactiveInterval = 1800;\n private readonly db: DBAdapter;\n\n constructor(db: DBAdapter) {\n this.db = db;\n }\n\n setDefaultMaxInactiveInterval(interval: number) {\n this.defaultMaxInactiveInterval = interval;\n }\n\n private mapSessionToEntity(session: DBSession): SessionEntity {\n const attributes: Record<string, string | number | null> = {};\n for (const name of session.getAttributeNames()) {\n attributes[name] = session.getAttribute(name);\n }\n const principalName = resolveIndexesFor(session);\n\n return {\n primaryId: session.primaryId,\n sessionId: session.getId(),\n isNew: session.isNew,\n creationTime: session.getCreationTime(),\n lastAccessTime: session.getLastAccessedTime(),\n maxInactiveInterval: session.getMaxInactiveInterval(),\n expiryTime: session.getLastAccessedTime() + session.getMaxInactiveInterval() * 1000,\n attributes,\n principalName,\n };\n }\n\n private mapEntityToSession(entity: SessionEntity, allowExpired: boolean): DBSession | null {\n const loaded = new MapSession(entity.sessionId);\n loaded.setCreationTime(entity.creationTime);\n loaded.setLastAccessedTime(entity.lastAccessTime);\n loaded.setMaxInactiveInterval(entity.maxInactiveInterval);\n for (const [key, value] of Object.entries(entity.attributes)) {\n loaded.setAttribute(key, value);\n }\n\n if (!allowExpired && loaded.isExpired()) return null;\n const session = new DBSession(loaded, false);\n session.primaryId = entity.primaryId;\n return session;\n }\n\n private async getSession(id: string, allowExpired: boolean): Promise<DBSession | null> {\n const entity = await this.db.findById(id);\n if (!entity) return null;\n return this.mapEntityToSession(entity, allowExpired);\n }\n\n // override\n createSession() {\n const cached = new MapSession();\n cached.setMaxInactiveInterval(this.defaultMaxInactiveInterval);\n return new DBSession(cached, true);\n }\n\n async save(session: DBSession) {\n const entity = this.mapSessionToEntity(session);\n await this.db.save(entity);\n session.isNew = false;\n }\n\n async findById(sessionId: string): Promise<DBSession | null> {\n return this.getSession(sessionId, false);\n }\n\n async deleteById(sessionId: string) {\n await this.db.deleteById(sessionId);\n }\n\n async findByIndexNameAndIndexValue(\n indexName: string,\n indexValue: string\n ): Promise<Map<string, DBSession>> {\n if (indexName !== PRINCIPAL_NAME_INDEX_NAME) {\n return new Map<string, DBSession>();\n }\n const entities = await this.db.findByPrincipalName(indexValue);\n if (entities.length === 0) {\n return new Map<string, DBSession>();\n }\n const sessions = new Map<string, DBSession>();\n for (const entity of entities) {\n const session = this.mapEntityToSession(entity, false);\n if (session) {\n sessions.set(session.getId(), session);\n }\n }\n return sessions;\n }\n\n async findByPrincipalName(principalName: string): Promise<Map<string, DBSession>> {\n return this.findByIndexNameAndIndexValue(PRINCIPAL_NAME_INDEX_NAME, principalName);\n }\n\n async cleanupExpiredSessions(cleanupCount?: number): Promise<void> {\n return this.db.cleanupExpiredSessions(cleanupCount);\n }\n}\n"],"mappings":";;;;AAyBA,IAAM,YAAN,cAAwB,WAAW;CACjC;CACA;CAEA,YAAY,QAAoB,OAAgB;EAC9C,MAAM,MAAM;EACZ,KAAK,QAAQ;EACb,KAAK,YAAY,WAAW;CAC9B;AACF;AAUA,IAAa,6BAAb,MAAgF;CAC9E,6BAAqC;CACrC;CAEA,YAAY,IAAe;EACzB,KAAK,KAAK;CACZ;CAEA,8BAA8B,UAAkB;EAC9C,KAAK,6BAA6B;CACpC;CAEA,mBAA2B,SAAmC;EAC5D,MAAM,aAAqD,CAAC;EAC5D,KAAK,MAAM,QAAQ,QAAQ,kBAAkB,GAC3C,WAAW,QAAQ,QAAQ,aAAa,IAAI;EAE9C,MAAM,gBAAgB,kBAAkB,OAAO;EAE/C,OAAO;GACL,WAAW,QAAQ;GACnB,WAAW,QAAQ,MAAM;GACzB,OAAO,QAAQ;GACf,cAAc,QAAQ,gBAAgB;GACtC,gBAAgB,QAAQ,oBAAoB;GAC5C,qBAAqB,QAAQ,uBAAuB;GACpD,YAAY,QAAQ,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;GAC/E;GACA;EACF;CACF;CAEA,mBAA2B,QAAuB,cAAyC;EACzF,MAAM,SAAS,IAAI,WAAW,OAAO,SAAS;EAC9C,OAAO,gBAAgB,OAAO,YAAY;EAC1C,OAAO,oBAAoB,OAAO,cAAc;EAChD,OAAO,uBAAuB,OAAO,mBAAmB;EACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,UAAU,GACzD,OAAO,aAAa,KAAK,KAAK;EAGhC,IAAI,CAAC,gBAAgB,OAAO,UAAU,GAAG,OAAO;EAChD,MAAM,UAAU,IAAI,UAAU,QAAQ,KAAK;EAC3C,QAAQ,YAAY,OAAO;EAC3B,OAAO;CACT;CAEA,MAAc,WAAW,IAAY,cAAkD;EACrF,MAAM,SAAS,MAAM,KAAK,GAAG,SAAS,EAAE;EACxC,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO,KAAK,mBAAmB,QAAQ,YAAY;CACrD;CAGA,gBAAgB;EACd,MAAM,SAAS,IAAI,WAAW;EAC9B,OAAO,uBAAuB,KAAK,0BAA0B;EAC7D,OAAO,IAAI,UAAU,QAAQ,IAAI;CACnC;CAEA,MAAM,KAAK,SAAoB;EAC7B,MAAM,SAAS,KAAK,mBAAmB,OAAO;EAC9C,MAAM,KAAK,GAAG,KAAK,MAAM;EACzB,QAAQ,QAAQ;CAClB;CAEA,MAAM,SAAS,WAA8C;EAC3D,OAAO,KAAK,WAAW,WAAW,KAAK;CACzC;CAEA,MAAM,WAAW,WAAmB;EAClC,MAAM,KAAK,GAAG,WAAW,SAAS;CACpC;CAEA,MAAM,6BACJ,WACA,YACiC;EACjC,IAAI,cAAA,0FACF,uBAAO,IAAI,IAAuB;EAEpC,MAAM,WAAW,MAAM,KAAK,GAAG,oBAAoB,UAAU;EAC7D,IAAI,SAAS,WAAW,GACtB,uBAAO,IAAI,IAAuB;EAEpC,MAAM,2BAAW,IAAI,IAAuB;EAC5C,KAAK,MAAM,UAAU,UAAU;GAC7B,MAAM,UAAU,KAAK,mBAAmB,QAAQ,KAAK;GACrD,IAAI,SACF,SAAS,IAAI,QAAQ,MAAM,GAAG,OAAO;EAEzC;EACA,OAAO;CACT;CAEA,MAAM,oBAAoB,eAAwD;EAChF,OAAO,KAAK,6BAA6B,2BAA2B,aAAa;CACnF;CAEA,MAAM,uBAAuB,cAAsC;EACjE,OAAO,KAAK,GAAG,uBAAuB,YAAY;CACpD;AACF"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/security",
3
- "version": "6.2.2",
3
+ "version": "6.3.0",
4
4
  "private": false,
5
5
  "repository": {
6
6
  "type": "git",