fastify-session-better-sqlite3-store 1.0.5
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/.github/workflows/ci.yml +37 -0
- package/.github/workflows/standard.yml +23 -0
- package/LICENSE +21 -0
- package/README.md +47 -0
- package/index.js +81 -0
- package/package.json +31 -0
- package/test/SmallTest.js +77 -0
- package/test/first.test.js +99 -0
- package/test/second.test.js +52 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
|
|
11
|
+
tests-ubuntu:
|
|
12
|
+
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
|
|
17
|
+
- uses: actions/checkout@v2
|
|
18
|
+
|
|
19
|
+
- name: Install Dependencies
|
|
20
|
+
run: npm install
|
|
21
|
+
|
|
22
|
+
- name: run tests
|
|
23
|
+
run: npm run test
|
|
24
|
+
|
|
25
|
+
tests-windows:
|
|
26
|
+
|
|
27
|
+
runs-on: windows-latest
|
|
28
|
+
|
|
29
|
+
steps:
|
|
30
|
+
|
|
31
|
+
- uses: actions/checkout@v2
|
|
32
|
+
|
|
33
|
+
- name: Install Dependencies
|
|
34
|
+
run: npm install
|
|
35
|
+
|
|
36
|
+
- name: run tests
|
|
37
|
+
run: npm run test
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: standard
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
|
|
11
|
+
check-code-style-standard:
|
|
12
|
+
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
|
|
15
|
+
steps:
|
|
16
|
+
|
|
17
|
+
- uses: actions/checkout@v2
|
|
18
|
+
|
|
19
|
+
- name: Install Standard
|
|
20
|
+
run: npm i -D standard
|
|
21
|
+
|
|
22
|
+
- name: Check code style format
|
|
23
|
+
run: npx standard *.js
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022 Mordecai Velasco
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# fastify-better-sqlite3-session-store
|
|
2
|
+
|
|
3
|
+

|
|
4
|
+

|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
|
|
7
|
+
Session store for [@fastify/session](https://github.com/fastify/session) using [better-sqlite3](https://github.com/WiseLibs/better-sqlite3). By default [@fastify/session](https://github.com/fastify/session) stores sessions in-memory. Using this package's class you can store sessions on an **SQLite3** database instead.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
npm install fastify-better-sqlite3-session-store
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Example
|
|
16
|
+
|
|
17
|
+
Use with `fastify-session`'s `store` property.
|
|
18
|
+
|
|
19
|
+
```js
|
|
20
|
+
const fastify = require('fastify')({logger:true})
|
|
21
|
+
const fastifyCookie = require('@fastify/cookie')
|
|
22
|
+
const fastifySession = require('@fastify/session')
|
|
23
|
+
const db = require('better-sqlite3')(`./sqlite.db`)
|
|
24
|
+
|
|
25
|
+
// require module
|
|
26
|
+
const SqliteStore = require('fastify-better-sqlite3-session-store')
|
|
27
|
+
|
|
28
|
+
fastify.register(fastifyCookie)
|
|
29
|
+
fastify.register(fastifySession,{
|
|
30
|
+
// ...
|
|
31
|
+
// other session options
|
|
32
|
+
// ...
|
|
33
|
+
store: new SqliteStore(db)
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
-----
|
|
38
|
+
|
|
39
|
+
## Package-Status
|
|
40
|
+
|
|
41
|
+
*premature, there are a lot of rooms for improvement*
|
|
42
|
+
|
|
43
|
+
-----
|
|
44
|
+
|
|
45
|
+
## License
|
|
46
|
+
|
|
47
|
+
[MIT Licence](https://github.com/mrdcvlsc/fastify-better-sqlite3-session-store/blob/main/LICENSE)
|
package/index.js
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const EventEmitter = require('events')
|
|
4
|
+
|
|
5
|
+
class SqliteStore extends EventEmitter {
|
|
6
|
+
/**
|
|
7
|
+
* @param {Object} sqlite3db database instance of better-sqlite3.
|
|
8
|
+
* @param {string} table table name where session data will be stored, defaults to `session`.
|
|
9
|
+
*/
|
|
10
|
+
constructor (sqlite3db, table = 'session') {
|
|
11
|
+
try {
|
|
12
|
+
sqlite3db.exec(`
|
|
13
|
+
create table ${table} (
|
|
14
|
+
sid TEXT PRIMARY KEY NOT NULL,
|
|
15
|
+
expires DATETIME NOT NULL,
|
|
16
|
+
session TEXT NOT NULL
|
|
17
|
+
)`
|
|
18
|
+
)
|
|
19
|
+
} catch (err) {
|
|
20
|
+
if (err.toString() !== 'SqliteError: table session already exists') {
|
|
21
|
+
throw Error(err.toString())
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
super()
|
|
25
|
+
this.setSession = sqlite3db.prepare(`INSERT INTO ${table} (sid, expires, session) VALUES (?, ?, ?)`)
|
|
26
|
+
this.getSession = sqlite3db.prepare(`SELECT sid, expires, session FROM ${table} WHERE sid = ?`)
|
|
27
|
+
this.destroySession = sqlite3db.prepare(`DELETE FROM ${table} WHERE sid = ?`)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
SqliteStore.prototype.set = function set (sessionId, session, callback) {
|
|
32
|
+
try {
|
|
33
|
+
this.setSession.run(
|
|
34
|
+
sessionId,
|
|
35
|
+
session.expires.toISOString(),
|
|
36
|
+
JSON.stringify(session)
|
|
37
|
+
)
|
|
38
|
+
callback(null)
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err.code === 'SQLITE_CONSTRAINT_PRIMARYKEY') {
|
|
41
|
+
callback(null)
|
|
42
|
+
} else {
|
|
43
|
+
callback(err)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
SqliteStore.prototype.get = function get (sessionId, callback) {
|
|
49
|
+
try {
|
|
50
|
+
const results = []
|
|
51
|
+
for (const row of this.getSession.iterate(sessionId)) {
|
|
52
|
+
results.push(row)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let session = null
|
|
56
|
+
if (results.length === 1) {
|
|
57
|
+
const found = JSON.parse(results[0].session)
|
|
58
|
+
if (found.expires) {
|
|
59
|
+
if (new Date() < new Date(found.expires)) {
|
|
60
|
+
session = found
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
session = found
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
callback(null, session)
|
|
67
|
+
} catch (err) {
|
|
68
|
+
callback(err)
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
SqliteStore.prototype.destroy = function destroy (sessionId, callback) {
|
|
73
|
+
try {
|
|
74
|
+
this.destroySession.run(sessionId)
|
|
75
|
+
callback(null)
|
|
76
|
+
} catch (err) {
|
|
77
|
+
callback(err)
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = SqliteStore
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "fastify-session-better-sqlite3-store",
|
|
3
|
+
"version": "1.0.5",
|
|
4
|
+
"description": "A simple session store for fastify-session using better-sqlite3",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"lint": "npx standard *.js",
|
|
8
|
+
"test": "node test/first.test && node test/second.test"
|
|
9
|
+
},
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/mrdcvlsc/fastify-session-better-sqlite3-store.git"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"session",
|
|
16
|
+
"fastify",
|
|
17
|
+
"store",
|
|
18
|
+
"better-sqlite3",
|
|
19
|
+
"sqlite",
|
|
20
|
+
"session-store"
|
|
21
|
+
],
|
|
22
|
+
"author": "mrdcvlsc",
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@fastify/cookie": "^7.2.0",
|
|
26
|
+
"@fastify/session": "^9.0.0",
|
|
27
|
+
"better-sqlite3": "^7.6.2",
|
|
28
|
+
"fastify": "^4.2.1",
|
|
29
|
+
"standard": "^17.0.0"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const fs = require('fs')
|
|
2
|
+
|
|
3
|
+
class SmallTest {
|
|
4
|
+
constructor (testName) {
|
|
5
|
+
this.verdicts = []
|
|
6
|
+
this.names = []
|
|
7
|
+
this.passing = true
|
|
8
|
+
this.testName = testName
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
assertEqual (name, a, b) {
|
|
12
|
+
this.names.push(name)
|
|
13
|
+
const result = (a === b)
|
|
14
|
+
this.verdicts.push(result ? 'PASSED' : 'FAILED')
|
|
15
|
+
if (!result) this.passing = false
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
results () {
|
|
19
|
+
console.log(`\n\nSmallTest : ${this.testName}\n`)
|
|
20
|
+
if (this.verdicts.length === 0) {
|
|
21
|
+
console.log('NO TESTS FOUND')
|
|
22
|
+
return false
|
|
23
|
+
}
|
|
24
|
+
for (let i = 0; i < this.verdicts.length; ++i) {
|
|
25
|
+
console.log(` ${this.verdicts[i]} : ${this.names[i]}`)
|
|
26
|
+
}
|
|
27
|
+
return this.passing
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function saveJsonFile (filename, json) {
|
|
32
|
+
try {
|
|
33
|
+
await fs.promises.writeFile(filename, JSON.stringify(json))
|
|
34
|
+
return Promise.resolve()
|
|
35
|
+
} catch (err) {
|
|
36
|
+
return Promise.reject(err)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readJsonFile (filename) {
|
|
41
|
+
try {
|
|
42
|
+
const json = JSON.parse(await fs.promises.readFile(filename))
|
|
43
|
+
return Promise.resolve(json)
|
|
44
|
+
} catch (err) {
|
|
45
|
+
return Promise.reject(err)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Simulate an http GET request, with session cookies.
|
|
51
|
+
* @param {Object} fastify instance of fastify application.
|
|
52
|
+
* @param {String} endpoint get request url.
|
|
53
|
+
* @param {Object} clientSessionCookie client session cookie key pair value.
|
|
54
|
+
* @returns {Promise<Response>}
|
|
55
|
+
*/
|
|
56
|
+
async function httpGetRequest (fastify, endpoint, clientSessionCookie) {
|
|
57
|
+
try {
|
|
58
|
+
const response = await fastify.inject({
|
|
59
|
+
method: 'GET',
|
|
60
|
+
url: endpoint,
|
|
61
|
+
cookies: clientSessionCookie
|
|
62
|
+
})
|
|
63
|
+
return Promise.resolve(response)
|
|
64
|
+
} catch (err) {
|
|
65
|
+
return Promise.reject(err)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function setTimeoutAsync (delay) {
|
|
70
|
+
return new Promise((resolve) => {
|
|
71
|
+
setTimeout(() => {
|
|
72
|
+
return resolve()
|
|
73
|
+
}, delay)
|
|
74
|
+
})
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
module.exports = { httpGetRequest, saveJsonFile, readJsonFile, SmallTest, setTimeoutAsync }
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const fastify = require('fastify')({ logger: true })
|
|
2
|
+
const fastifyCookie = require('@fastify/cookie')
|
|
3
|
+
const fastifySession = require('@fastify/session')
|
|
4
|
+
const sqlite3db = require('better-sqlite3')('./test/sqlite.db')
|
|
5
|
+
|
|
6
|
+
const SqliteStore = require('../index')
|
|
7
|
+
|
|
8
|
+
fastify.register(fastifyCookie)
|
|
9
|
+
fastify.register(fastifySession, {
|
|
10
|
+
secret: 'a super duper secret session string',
|
|
11
|
+
cookie: {
|
|
12
|
+
secure: false,
|
|
13
|
+
maxAge: 5000
|
|
14
|
+
},
|
|
15
|
+
store: new SqliteStore(sqlite3db),
|
|
16
|
+
saveUninitialized: false
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
fastify.get('/set/:name', (request, reply) => {
|
|
20
|
+
request.session.name = request.params.name
|
|
21
|
+
reply.send(`recorded:${request.session.name}`)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
fastify.get('/destroy', async (request, reply) => {
|
|
25
|
+
try {
|
|
26
|
+
if (request.session.name) {
|
|
27
|
+
const destroyedUser = request.session.name
|
|
28
|
+
await request.session.destroy()
|
|
29
|
+
return reply.send(`destroyed:${destroyedUser}`)
|
|
30
|
+
} else {
|
|
31
|
+
return reply.send('destroyed:no-user')
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
fastify.log.error(err)
|
|
35
|
+
return process.exit(1)
|
|
36
|
+
}
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
fastify.get('/session', (request, reply) => {
|
|
40
|
+
if (request.session.name) {
|
|
41
|
+
reply.send(`session:${request.session.name}`)
|
|
42
|
+
} else {
|
|
43
|
+
reply.send('session:no-user')
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const { SmallTest, httpGetRequest, saveJsonFile, setTimeoutAsync } = require('./SmallTest')
|
|
48
|
+
|
|
49
|
+
async function start () {
|
|
50
|
+
const t = new SmallTest('TEST SET CASE 1')
|
|
51
|
+
try {
|
|
52
|
+
await fastify.listen({ port: 3000 })
|
|
53
|
+
|
|
54
|
+
const subject1 = await httpGetRequest(fastify, '/session')
|
|
55
|
+
const subject2 = await httpGetRequest(fastify, '/set/subject2')
|
|
56
|
+
const cookie1 = {
|
|
57
|
+
sessionId: subject2.cookies[0].value
|
|
58
|
+
}
|
|
59
|
+
const subject3 = await httpGetRequest(fastify, '/session', cookie1)
|
|
60
|
+
const subject4 = await httpGetRequest(fastify, '/destroy', cookie1)
|
|
61
|
+
const subject5 = await httpGetRequest(fastify, '/session', cookie1)
|
|
62
|
+
const subject6 = await httpGetRequest(fastify, '/set/subject6')
|
|
63
|
+
const cookie2 = {
|
|
64
|
+
sessionId: subject6.cookies[0].value
|
|
65
|
+
}
|
|
66
|
+
const subject7 = await httpGetRequest(fastify, '/session', cookie2)
|
|
67
|
+
await setTimeoutAsync(8000)
|
|
68
|
+
const subject8 = await httpGetRequest(fastify, '/session', cookie2)
|
|
69
|
+
const subject9 = await httpGetRequest(fastify, '/set/subject9')
|
|
70
|
+
const cookie3 = {
|
|
71
|
+
sessionId: subject9.cookies[0].value
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
t.assertEqual('session none', subject1.payload, 'session:no-user')
|
|
75
|
+
t.assertEqual('session recorded', subject2.payload, 'recorded:subject2')
|
|
76
|
+
t.assertEqual('session existing', subject3.payload, 'session:subject2')
|
|
77
|
+
t.assertEqual('session destroyed', subject4.payload, 'destroyed:subject2')
|
|
78
|
+
t.assertEqual('session no more', subject5.payload, 'session:no-user')
|
|
79
|
+
t.assertEqual('new session recorded', subject6.payload, 'recorded:subject6')
|
|
80
|
+
t.assertEqual('new session existing', subject7.payload, 'session:subject6')
|
|
81
|
+
t.assertEqual('new session expired', subject8.payload, 'session:no-user')
|
|
82
|
+
t.assertEqual('session saved then server shutsdown', subject9.payload, 'recorded:subject9')
|
|
83
|
+
|
|
84
|
+
await fastify.close()
|
|
85
|
+
await saveJsonFile('test/cookie.json', cookie3)
|
|
86
|
+
|
|
87
|
+
if (t.results()) {
|
|
88
|
+
console.log(t.testName, '=> PASSED ALL TEST\n')
|
|
89
|
+
} else {
|
|
90
|
+
console.log(t.testName, '=> FAILED SOME TEST\n')
|
|
91
|
+
process.exit(1)
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
fastify.log.error(err)
|
|
95
|
+
process.exit(1)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
start()
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
const fastify = require('fastify')({ logger: true })
|
|
2
|
+
const fastifyCookie = require('@fastify/cookie')
|
|
3
|
+
const fastifySession = require('@fastify/session')
|
|
4
|
+
const sqlite3db = require('better-sqlite3')('./test/sqlite.db')
|
|
5
|
+
|
|
6
|
+
const SqliteStore = require('../index')
|
|
7
|
+
|
|
8
|
+
fastify.register(fastifyCookie)
|
|
9
|
+
fastify.register(fastifySession, {
|
|
10
|
+
secret: 'a super duper secret session string',
|
|
11
|
+
cookie: {
|
|
12
|
+
secure: false,
|
|
13
|
+
maxAge: 5000
|
|
14
|
+
},
|
|
15
|
+
store: new SqliteStore(sqlite3db),
|
|
16
|
+
saveUninitialized: false
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
fastify.get('/session', (request, reply) => {
|
|
20
|
+
if (request.session.name) {
|
|
21
|
+
reply.send(`session:${request.session.name}`)
|
|
22
|
+
} else {
|
|
23
|
+
reply.send('session:no-user')
|
|
24
|
+
}
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const { SmallTest, httpGetRequest, readJsonFile } = require('./SmallTest')
|
|
28
|
+
|
|
29
|
+
async function start () {
|
|
30
|
+
const t = new SmallTest('TEST SET CASE 2')
|
|
31
|
+
try {
|
|
32
|
+
await fastify.listen({ port: 3001 })
|
|
33
|
+
|
|
34
|
+
const cookie = await readJsonFile('test/cookie.json')
|
|
35
|
+
const subject9 = await httpGetRequest(fastify, '/session', cookie)
|
|
36
|
+
t.assertEqual('session existing after server restart', subject9.payload, 'session:subject9')
|
|
37
|
+
|
|
38
|
+
await fastify.close()
|
|
39
|
+
|
|
40
|
+
if (t.results()) {
|
|
41
|
+
console.log(t.testName, '=> PASSED ALL TEST\n')
|
|
42
|
+
} else {
|
|
43
|
+
console.log(t.testName, '=> FAILED SOME TEST\n')
|
|
44
|
+
process.exit(1)
|
|
45
|
+
}
|
|
46
|
+
} catch (err) {
|
|
47
|
+
fastify.log.error(err)
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
start()
|