session-sync-auth-site 0.2.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 +123 -0
- package/package.json +52 -0
- package/src/authenticate.js +75 -0
- package/src/createDBTables.js +97 -0
- package/src/index.js +7 -0
- package/src/iso639-3To1.js +190 -0
- package/src/sessionSyncAuthFrontend.js +102 -0
- package/src/setUpConnection.js +44 -0
- package/src/setUpSessionSyncAuthRoutes.js +204 -0
- package/test/app.html +82 -0
- package/test/dummySite.js +115 -0
package/README.md
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Setup
|
|
2
|
+
|
|
3
|
+
Run `node ./node_modules/session-sync-auth-site/src/createDBTables.js [mysql_connection_string] [user_table_name] [session_table_name]`
|
|
4
|
+
|
|
5
|
+
Example: `node ./node_modules/session-sync-auth-site/src/createDBTables.js mysql://root@localhost/SessionSyncAuthSite users sessions`
|
|
6
|
+
|
|
7
|
+
You may add on more fields to the user and session tables, if you like.
|
|
8
|
+
|
|
9
|
+
# Simple backend usage
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
const express = require('express')
|
|
13
|
+
const app = express()
|
|
14
|
+
const cors = require('cors')
|
|
15
|
+
const bodyParser = require('body-parser')
|
|
16
|
+
|
|
17
|
+
const { authenticate, setUpSessionSyncAuthRoutes } = require('session-sync-auth-site')
|
|
18
|
+
|
|
19
|
+
app.use(cors())
|
|
20
|
+
app.use(bodyParser.json())
|
|
21
|
+
|
|
22
|
+
app.use(authenticate({
|
|
23
|
+
// either `connectionObj` or `connectionStr` is required
|
|
24
|
+
connectionObj: {
|
|
25
|
+
host,
|
|
26
|
+
user,
|
|
27
|
+
password,
|
|
28
|
+
database,
|
|
29
|
+
port,
|
|
30
|
+
},
|
|
31
|
+
}))
|
|
32
|
+
|
|
33
|
+
setUpSessionSyncAuthRoutes({
|
|
34
|
+
app,
|
|
35
|
+
siteId,
|
|
36
|
+
authDomain,
|
|
37
|
+
jwtSecret,
|
|
38
|
+
})
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Exhaustive options for authenticate with default values
|
|
42
|
+
|
|
43
|
+
```js
|
|
44
|
+
app.use(authenticate({
|
|
45
|
+
// either `connectionObj` or `connectionStr` required
|
|
46
|
+
connectionObj: {
|
|
47
|
+
host,
|
|
48
|
+
user,
|
|
49
|
+
password,
|
|
50
|
+
database,
|
|
51
|
+
port,
|
|
52
|
+
},
|
|
53
|
+
userTableName: 'users',
|
|
54
|
+
sessionTableName: 'sessions',
|
|
55
|
+
userTableColNameMap: {
|
|
56
|
+
// Example:
|
|
57
|
+
// updated_at: 'updatedAt',
|
|
58
|
+
},
|
|
59
|
+
sessionTableColNameMap: {},
|
|
60
|
+
}))
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Exhaustive options for setUpSessionSyncAuthRoutes with default values
|
|
64
|
+
|
|
65
|
+
```js
|
|
66
|
+
setUpSessionSyncAuthRoutes({
|
|
67
|
+
app, // required
|
|
68
|
+
siteId, // required
|
|
69
|
+
authDomain, // required
|
|
70
|
+
jwtSecret, // required
|
|
71
|
+
protocol: 'https',
|
|
72
|
+
paths: {
|
|
73
|
+
getUser: '/get-user',
|
|
74
|
+
logIn: '/log-in',
|
|
75
|
+
logOut: '/log-out',
|
|
76
|
+
authSync: '/auth-sync',
|
|
77
|
+
},
|
|
78
|
+
languageColType: '639-3', // OPTIONS: '639-1', '639-3', 'IETF'
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
# Frontend usage
|
|
83
|
+
|
|
84
|
+
```html
|
|
85
|
+
<html>
|
|
86
|
+
<head>
|
|
87
|
+
<script src="[private_url]/sessionSyncAuthFrontend.js"></script>
|
|
88
|
+
|
|
89
|
+
<script>
|
|
90
|
+
|
|
91
|
+
window.sessionSyncAuth.init({
|
|
92
|
+
defaultOrigin: 'https://my-backend-domain.com',
|
|
93
|
+
callbacks: {
|
|
94
|
+
canceledLogin: ({ origin }) => {},
|
|
95
|
+
successfulLogin: ({ origin, accessToken }) => {},
|
|
96
|
+
successfulLogout: ({ origin }) => {},
|
|
97
|
+
unnecessaryLogout: ({ origin }) => {},
|
|
98
|
+
error: ({ errorMessage }) => {},
|
|
99
|
+
},
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
// To change the default origin...
|
|
103
|
+
// window.sessionSyncAuth.setDefaultOrigin('https://my-backend-domain.com')
|
|
104
|
+
|
|
105
|
+
</script>
|
|
106
|
+
<head>
|
|
107
|
+
|
|
108
|
+
<body>
|
|
109
|
+
|
|
110
|
+
<!-- All functions below can also take a single options parameter with an `origin` key. -->
|
|
111
|
+
|
|
112
|
+
<button onclick="javascript:window.sessionSyncAuth.getAccessToken()">Get Access Token</button>
|
|
113
|
+
|
|
114
|
+
<button onclick="javascript:window.sessionSyncAuth.logIn()">Log in</button>
|
|
115
|
+
|
|
116
|
+
<button onclick="javascript:window.sessionSyncAuth.getUser()">Get user</button>
|
|
117
|
+
|
|
118
|
+
<button onclick="javascript:window.sessionSyncAuth.logOut()">Log out</button>
|
|
119
|
+
|
|
120
|
+
</body>
|
|
121
|
+
|
|
122
|
+
</html>
|
|
123
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "session-sync-auth-site",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"main": "src/index.js",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/educational-resources-and-services/session-sync-auth-site.git"
|
|
8
|
+
},
|
|
9
|
+
"author": "Andy Hubert",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/educational-resources-and-services/session-sync-auth-site/issues"
|
|
13
|
+
},
|
|
14
|
+
"homepage": "https://github.com/educational-resources-and-services/session-sync-auth-site#readme",
|
|
15
|
+
"engines": {
|
|
16
|
+
"node": ">=10"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"dev": "npm run go-dev -s",
|
|
20
|
+
"go-dev": "concurrently -k 'node ./test/dummySite.js 3001' 'node ./test/dummySite.js 3002 dummy1' 'node ./test/dummySite.js 3003 dummy2'",
|
|
21
|
+
"dev-with-staging-auth": "npm run go-dev-with-staging-auth -s",
|
|
22
|
+
"go-dev-with-staging-auth": "concurrently -k 'node ./test/dummySite.js 3001' 'node ./test/dummySite.js 3002 dummy1 auth.staging.resourcingeducation.com' 'node ./test/dummySite.js 3003 dummy2 auth.staging.resourcingeducation.com'",
|
|
23
|
+
"dev-with-production-auth": "npm run go-dev-with-production-auth -s",
|
|
24
|
+
"go-dev-with-production-auth": "concurrently -k 'node ./test/dummySite.js 3001' 'node ./test/dummySite.js 3002 dummy1 auth.resourcingeducation.com' 'node ./test/dummySite.js 3003 dummy2 auth.resourcingeducation.com'",
|
|
25
|
+
"setup": "npm run go-setup -s",
|
|
26
|
+
"go-setup": "npm run go-setup-dummy1 && npm run go-setup-dummy2",
|
|
27
|
+
"go-setup-dummy1": "node ./src/createDBTables.js mysql://root@localhost/SessionSyncAuthSite dummy1_users dummy1_sessions",
|
|
28
|
+
"go-setup-dummy2": "node ./src/createDBTables.js mysql://root@localhost/SessionSyncAuthSite dummy2_users dummy2_sessions",
|
|
29
|
+
"confirm": "read -p 'Are you sure? ' -n 1 -r && echo '\n' && [[ $REPLY =~ ^[Yy]$ ]]",
|
|
30
|
+
"update-patch": "npm run go-update-patch -s",
|
|
31
|
+
"update-major": "npm run go-update-major -s",
|
|
32
|
+
"update-minor": "npm run go-update-minor -s",
|
|
33
|
+
"go-update-patch": "echo '-------------------------------------------\nUpdate version (PATCH) and deploy to npm...\n-------------------------------------------\n' && npm run confirm && npm i && npm version patch && npm run publish-to-npm",
|
|
34
|
+
"go-update-minor": "echo '-------------------------------------------\nUpdate version (MINOR) and deploy to npm...\n-------------------------------------------\n' && npm run confirm && npm i && npm version minor && npm run publish-to-npm",
|
|
35
|
+
"go-update-major": "echo '-------------------------------------------\nUpdate version (MAJOR) and deploy to npm...\n-------------------------------------------\n' && npm run confirm && npm i && npm version major && npm run publish-to-npm",
|
|
36
|
+
"publish-to-npm": "npm publish --access public && echo '\nSUCCESS!\n'"
|
|
37
|
+
},
|
|
38
|
+
"peerDependencies": {
|
|
39
|
+
"express": ">= 4"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"body-parser": "^1.19.0",
|
|
43
|
+
"concurrently": "^6.0.2",
|
|
44
|
+
"cors": "^2.8.5",
|
|
45
|
+
"express": "^4.17.1"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"connection-string": "^4.3.2",
|
|
49
|
+
"jsonwebtoken": "^8.5.1",
|
|
50
|
+
"mysql": "^2.18.1"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
const setUpConnection = require('./setUpConnection')
|
|
2
|
+
|
|
3
|
+
const authenticate = ({
|
|
4
|
+
userTableName='users',
|
|
5
|
+
sessionTableName='sessions',
|
|
6
|
+
userTableColNameMap={},
|
|
7
|
+
sessionTableColNameMap={},
|
|
8
|
+
...connectionInfo // connectionObj or connectionStr
|
|
9
|
+
}) => (
|
|
10
|
+
async (req, res, next) => {
|
|
11
|
+
|
|
12
|
+
userTableName = userTableName.replace(/`/g, '')
|
|
13
|
+
sessionTableName = sessionTableName.replace(/`/g, '')
|
|
14
|
+
|
|
15
|
+
req.sessionSyncAuthSiteOptions = {
|
|
16
|
+
userTableName,
|
|
17
|
+
sessionTableName,
|
|
18
|
+
userTableColNameMap,
|
|
19
|
+
sessionTableColNameMap,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Connect to DB if not already connected
|
|
23
|
+
if(!global.sessionSyncAuthSiteConnection) {
|
|
24
|
+
console.log('Establishing DB connection for session-sync-auth-site...')
|
|
25
|
+
setUpConnection(connectionInfo)
|
|
26
|
+
console.log('...DB connection established for session-sync-auth-site.')
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const accessToken = req.headers['x-access-token'] || req.query.accessToken
|
|
30
|
+
|
|
31
|
+
if(!accessToken) return next()
|
|
32
|
+
|
|
33
|
+
const userTableId = (userTableColNameMap.id || 'id').replace(/`/g, '')
|
|
34
|
+
const sessionTableCreatedAt = (sessionTableColNameMap.created_at || 'created_at').replace(/`/g, '')
|
|
35
|
+
const sessionTableUserId = (sessionTableColNameMap.user_id || 'user_id').replace(/`/g, '')
|
|
36
|
+
const sessionTableAccessToken = (sessionTableColNameMap.access_token || 'access_token').replace(/`/g, '')
|
|
37
|
+
|
|
38
|
+
req.user = (await global.sessionSyncAuthSiteConnection.asyncQuery(
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
SELECT
|
|
42
|
+
u.*,
|
|
43
|
+
s.\`${sessionTableCreatedAt}\` AS session_created_at
|
|
44
|
+
|
|
45
|
+
FROM \`${userTableName}\` AS u
|
|
46
|
+
LEFT JOIN \`${sessionTableName}\` AS s ON (u.\`${userTableId}\` = s.\`${sessionTableUserId}\`)
|
|
47
|
+
|
|
48
|
+
WHERE
|
|
49
|
+
s.\`${sessionTableAccessToken}\` = :accessToken
|
|
50
|
+
|
|
51
|
+
LIMIT 1
|
|
52
|
+
|
|
53
|
+
`,
|
|
54
|
+
{
|
|
55
|
+
accessToken,
|
|
56
|
+
},
|
|
57
|
+
))[0]
|
|
58
|
+
|
|
59
|
+
// Convert DateTime columns into ms timestamp
|
|
60
|
+
const dateTimeCols = [
|
|
61
|
+
userTableColNameMap.created_at || 'created_at',
|
|
62
|
+
userTableColNameMap.updated_at || 'updated_at',
|
|
63
|
+
'session_created_at',
|
|
64
|
+
]
|
|
65
|
+
Object.keys(req.user || {}).forEach(key => {
|
|
66
|
+
if(dateTimeCols.includes(key)) {
|
|
67
|
+
req.user[key] = (new Date(req.user[key])).getTime()
|
|
68
|
+
}
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
next()
|
|
72
|
+
}
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
module.exports = authenticate
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const setUpConnection = require('./setUpConnection')
|
|
2
|
+
|
|
3
|
+
;(async () => {
|
|
4
|
+
|
|
5
|
+
try {
|
|
6
|
+
|
|
7
|
+
let [
|
|
8
|
+
connectionStr,
|
|
9
|
+
userTableName=`users`,
|
|
10
|
+
sessionTableName=`sessions`,
|
|
11
|
+
x,
|
|
12
|
+
] = process.argv.slice(2)
|
|
13
|
+
|
|
14
|
+
userTableName = userTableName.replace(/`/g, '')
|
|
15
|
+
sessionTableName = sessionTableName.replace(/`/g, '')
|
|
16
|
+
|
|
17
|
+
if(connectionStr === undefined) {
|
|
18
|
+
throw new Error(`NO_PARAMS`)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
if(userTableName === undefined || sessionTableName === undefined || x !== undefined) {
|
|
22
|
+
throw new Error(`BAD_PARAMS`)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
setUpConnection({ connectionStr })
|
|
26
|
+
|
|
27
|
+
await global.sessionSyncAuthSiteConnection.asyncQuery(
|
|
28
|
+
`
|
|
29
|
+
|
|
30
|
+
CREATE TABLE \`${userTableName}\` (
|
|
31
|
+
\`id\` int NOT NULL,
|
|
32
|
+
\`name\` varchar(255),
|
|
33
|
+
\`email\` varchar(255),
|
|
34
|
+
\`image\` varchar(255),
|
|
35
|
+
\`language\` varchar(25),
|
|
36
|
+
\`created_at\` datetime(3) NOT NULL,
|
|
37
|
+
\`updated_at\` datetime(3) NOT NULL,
|
|
38
|
+
PRIMARY KEY (\`id\`),
|
|
39
|
+
UNIQUE KEY \`email\` (\`email\`)
|
|
40
|
+
) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
41
|
+
|
|
42
|
+
CREATE TABLE \`${sessionTableName}\` (
|
|
43
|
+
\`id\` int NOT NULL AUTO_INCREMENT,
|
|
44
|
+
\`user_id\` int NOT NULL,
|
|
45
|
+
\`access_token\` varchar(255) NOT NULL,
|
|
46
|
+
\`created_at\` datetime(3) NOT NULL,
|
|
47
|
+
PRIMARY KEY (\`id\`),
|
|
48
|
+
UNIQUE KEY \`access_token\` (\`access_token\`)
|
|
49
|
+
) CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
50
|
+
|
|
51
|
+
`,
|
|
52
|
+
{},
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
console.log(``)
|
|
56
|
+
console.log(`Tables ${userTableName} and ${sessionTableName} created.`)
|
|
57
|
+
console.log(``)
|
|
58
|
+
console.log(`Note: The user table may be extended with other columns so long as they are nullable.`)
|
|
59
|
+
console.log(``)
|
|
60
|
+
|
|
61
|
+
} catch(err) {
|
|
62
|
+
|
|
63
|
+
const logSyntax = () => {
|
|
64
|
+
console.log(`Syntax: \`npm run create-db-tables connectionStr [userTableName] [sessionTableName]\`\n`)
|
|
65
|
+
console.log(`Example #1: \`npm run create-db-tables mysql://user:pass@host/db\``)
|
|
66
|
+
console.log(`Example #2: \`npm run create-db-tables mysql://user:pass@host/db users sessions\``)
|
|
67
|
+
console.log(``)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
switch(err.message.split(',')[0]) {
|
|
71
|
+
|
|
72
|
+
case `NO_PARAMS`: {
|
|
73
|
+
logSyntax()
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case `BAD_PARAMS`: {
|
|
78
|
+
console.log(`\n-----------------------`)
|
|
79
|
+
console.log(`ERROR: Bad parameters.`)
|
|
80
|
+
console.log(`-----------------------\n`)
|
|
81
|
+
logSyntax()
|
|
82
|
+
break
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
default: {
|
|
86
|
+
console.log(`\n-----------------------`)
|
|
87
|
+
console.log(`\nERROR: ${err.message}\n`)
|
|
88
|
+
console.log(`-----------------------\n`)
|
|
89
|
+
logSyntax()
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
process.exit()
|
|
96
|
+
|
|
97
|
+
})()
|
package/src/index.js
ADDED
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
// Converted from https://github.com/wooorm/iso-639-3/blob/main/iso6393-to-1.js
|
|
2
|
+
|
|
3
|
+
const iso6393To1 = {
|
|
4
|
+
aar: 'aa',
|
|
5
|
+
abk: 'ab',
|
|
6
|
+
afr: 'af',
|
|
7
|
+
aka: 'ak',
|
|
8
|
+
amh: 'am',
|
|
9
|
+
ara: 'ar',
|
|
10
|
+
arg: 'an',
|
|
11
|
+
asm: 'as',
|
|
12
|
+
ava: 'av',
|
|
13
|
+
ave: 'ae',
|
|
14
|
+
aym: 'ay',
|
|
15
|
+
aze: 'az',
|
|
16
|
+
bak: 'ba',
|
|
17
|
+
bam: 'bm',
|
|
18
|
+
bel: 'be',
|
|
19
|
+
ben: 'bn',
|
|
20
|
+
bis: 'bi',
|
|
21
|
+
bod: 'bo',
|
|
22
|
+
bos: 'bs',
|
|
23
|
+
bre: 'br',
|
|
24
|
+
bul: 'bg',
|
|
25
|
+
cat: 'ca',
|
|
26
|
+
ces: 'cs',
|
|
27
|
+
cha: 'ch',
|
|
28
|
+
che: 'ce',
|
|
29
|
+
chu: 'cu',
|
|
30
|
+
chv: 'cv',
|
|
31
|
+
cor: 'kw',
|
|
32
|
+
cos: 'co',
|
|
33
|
+
cre: 'cr',
|
|
34
|
+
cym: 'cy',
|
|
35
|
+
dan: 'da',
|
|
36
|
+
deu: 'de',
|
|
37
|
+
div: 'dv',
|
|
38
|
+
dzo: 'dz',
|
|
39
|
+
ell: 'el',
|
|
40
|
+
eng: 'en',
|
|
41
|
+
epo: 'eo',
|
|
42
|
+
est: 'et',
|
|
43
|
+
eus: 'eu',
|
|
44
|
+
ewe: 'ee',
|
|
45
|
+
fao: 'fo',
|
|
46
|
+
fas: 'fa',
|
|
47
|
+
fij: 'fj',
|
|
48
|
+
fin: 'fi',
|
|
49
|
+
fra: 'fr',
|
|
50
|
+
fry: 'fy',
|
|
51
|
+
ful: 'ff',
|
|
52
|
+
gla: 'gd',
|
|
53
|
+
gle: 'ga',
|
|
54
|
+
glg: 'gl',
|
|
55
|
+
glv: 'gv',
|
|
56
|
+
grn: 'gn',
|
|
57
|
+
guj: 'gu',
|
|
58
|
+
hat: 'ht',
|
|
59
|
+
hau: 'ha',
|
|
60
|
+
hbs: 'sh',
|
|
61
|
+
heb: 'he',
|
|
62
|
+
her: 'hz',
|
|
63
|
+
hin: 'hi',
|
|
64
|
+
hmo: 'ho',
|
|
65
|
+
hrv: 'hr',
|
|
66
|
+
hun: 'hu',
|
|
67
|
+
hye: 'hy',
|
|
68
|
+
ibo: 'ig',
|
|
69
|
+
ido: 'io',
|
|
70
|
+
iii: 'ii',
|
|
71
|
+
iku: 'iu',
|
|
72
|
+
ile: 'ie',
|
|
73
|
+
ina: 'ia',
|
|
74
|
+
ind: 'id',
|
|
75
|
+
ipk: 'ik',
|
|
76
|
+
isl: 'is',
|
|
77
|
+
ita: 'it',
|
|
78
|
+
jav: 'jv',
|
|
79
|
+
jpn: 'ja',
|
|
80
|
+
kal: 'kl',
|
|
81
|
+
kan: 'kn',
|
|
82
|
+
kas: 'ks',
|
|
83
|
+
kat: 'ka',
|
|
84
|
+
kau: 'kr',
|
|
85
|
+
kaz: 'kk',
|
|
86
|
+
khm: 'km',
|
|
87
|
+
kik: 'ki',
|
|
88
|
+
kin: 'rw',
|
|
89
|
+
kir: 'ky',
|
|
90
|
+
kom: 'kv',
|
|
91
|
+
kon: 'kg',
|
|
92
|
+
kor: 'ko',
|
|
93
|
+
kua: 'kj',
|
|
94
|
+
kur: 'ku',
|
|
95
|
+
lao: 'lo',
|
|
96
|
+
lat: 'la',
|
|
97
|
+
lav: 'lv',
|
|
98
|
+
lim: 'li',
|
|
99
|
+
lin: 'ln',
|
|
100
|
+
lit: 'lt',
|
|
101
|
+
ltz: 'lb',
|
|
102
|
+
lub: 'lu',
|
|
103
|
+
lug: 'lg',
|
|
104
|
+
mah: 'mh',
|
|
105
|
+
mal: 'ml',
|
|
106
|
+
mar: 'mr',
|
|
107
|
+
mkd: 'mk',
|
|
108
|
+
mlg: 'mg',
|
|
109
|
+
mlt: 'mt',
|
|
110
|
+
mon: 'mn',
|
|
111
|
+
mri: 'mi',
|
|
112
|
+
msa: 'ms',
|
|
113
|
+
mya: 'my',
|
|
114
|
+
nau: 'na',
|
|
115
|
+
nav: 'nv',
|
|
116
|
+
nbl: 'nr',
|
|
117
|
+
nde: 'nd',
|
|
118
|
+
ndo: 'ng',
|
|
119
|
+
nep: 'ne',
|
|
120
|
+
nld: 'nl',
|
|
121
|
+
nno: 'nn',
|
|
122
|
+
nob: 'nb',
|
|
123
|
+
nor: 'no',
|
|
124
|
+
nya: 'ny',
|
|
125
|
+
oci: 'oc',
|
|
126
|
+
oji: 'oj',
|
|
127
|
+
ori: 'or',
|
|
128
|
+
orm: 'om',
|
|
129
|
+
oss: 'os',
|
|
130
|
+
pan: 'pa',
|
|
131
|
+
pli: 'pi',
|
|
132
|
+
pol: 'pl',
|
|
133
|
+
por: 'pt',
|
|
134
|
+
pus: 'ps',
|
|
135
|
+
que: 'qu',
|
|
136
|
+
roh: 'rm',
|
|
137
|
+
ron: 'ro',
|
|
138
|
+
run: 'rn',
|
|
139
|
+
rus: 'ru',
|
|
140
|
+
sag: 'sg',
|
|
141
|
+
san: 'sa',
|
|
142
|
+
sin: 'si',
|
|
143
|
+
slk: 'sk',
|
|
144
|
+
slv: 'sl',
|
|
145
|
+
sme: 'se',
|
|
146
|
+
smo: 'sm',
|
|
147
|
+
sna: 'sn',
|
|
148
|
+
snd: 'sd',
|
|
149
|
+
som: 'so',
|
|
150
|
+
sot: 'st',
|
|
151
|
+
spa: 'es',
|
|
152
|
+
sqi: 'sq',
|
|
153
|
+
srd: 'sc',
|
|
154
|
+
srp: 'sr',
|
|
155
|
+
ssw: 'ss',
|
|
156
|
+
sun: 'su',
|
|
157
|
+
swa: 'sw',
|
|
158
|
+
swe: 'sv',
|
|
159
|
+
tah: 'ty',
|
|
160
|
+
tam: 'ta',
|
|
161
|
+
tat: 'tt',
|
|
162
|
+
tel: 'te',
|
|
163
|
+
tgk: 'tg',
|
|
164
|
+
tgl: 'tl',
|
|
165
|
+
tha: 'th',
|
|
166
|
+
tir: 'ti',
|
|
167
|
+
ton: 'to',
|
|
168
|
+
tsn: 'tn',
|
|
169
|
+
tso: 'ts',
|
|
170
|
+
tuk: 'tk',
|
|
171
|
+
tur: 'tr',
|
|
172
|
+
twi: 'tw',
|
|
173
|
+
uig: 'ug',
|
|
174
|
+
ukr: 'uk',
|
|
175
|
+
urd: 'ur',
|
|
176
|
+
uzb: 'uz',
|
|
177
|
+
ven: 've',
|
|
178
|
+
vie: 'vi',
|
|
179
|
+
vol: 'vo',
|
|
180
|
+
wln: 'wa',
|
|
181
|
+
wol: 'wo',
|
|
182
|
+
xho: 'xh',
|
|
183
|
+
yid: 'yi',
|
|
184
|
+
yor: 'yo',
|
|
185
|
+
zha: 'za',
|
|
186
|
+
zho: 'zh',
|
|
187
|
+
zul: 'zu'
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
module.exports = iso6393To1
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
;(() => {
|
|
2
|
+
|
|
3
|
+
let defaultOrigin
|
|
4
|
+
const setDefaultOrigin = origin => {
|
|
5
|
+
defaultOrigin = origin
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const getAccessToken = ({ origin=defaultOrigin }={}) => (
|
|
9
|
+
JSON.parse(localStorage.getItem('sessionSyncAuthAccessTokenByOrigin') || `{}`)[origin]
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const getQueryStringAddOn = extraQueryParamsForCallbacks => {
|
|
13
|
+
let queryStringAddOn = ''
|
|
14
|
+
|
|
15
|
+
for(let key in extraQueryParamsForCallbacks) {
|
|
16
|
+
queryStringAddOn += `&${encodeURIComponent(key)}=${encodeURIComponent(extraQueryParamsForCallbacks[key])}`
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return queryStringAddOn
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const logIn = async ({ origin=defaultOrigin, extraQueryParamsForCallbacks }={}) => {
|
|
23
|
+
const cancelRedirectUrl = `${location.href.replace(/\?.*$/, '')}?action=canceledLogin&origin=${encodeURIComponent(origin)}${getQueryStringAddOn(extraQueryParamsForCallbacks)}`
|
|
24
|
+
const loggedInRedirectUrl = `${location.href.replace(/\?.*$/, '')}?action=successfulLogin&origin=${encodeURIComponent(origin)}&accessToken=ACCESS_TOKEN${getQueryStringAddOn(extraQueryParamsForCallbacks)}`
|
|
25
|
+
|
|
26
|
+
const queryString = `cancelRedirectUrl=${encodeURIComponent(cancelRedirectUrl)}&loggedInRedirectUrl=${encodeURIComponent(loggedInRedirectUrl)}`
|
|
27
|
+
|
|
28
|
+
window.location = `${origin}/log-in?${queryString}`
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const getUser = async ({ origin=defaultOrigin }={}) => {
|
|
32
|
+
const response = await fetch(`${origin}/get-user`, {
|
|
33
|
+
headers: {
|
|
34
|
+
'x-access-token': getAccessToken(origin),
|
|
35
|
+
},
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
return await response.json()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const logOut = async ({ origin=defaultOrigin, extraQueryParamsForCallbacks }={}) => {
|
|
42
|
+
const redirectUrl = `${location.href.replace(/\?.*$/, '')}?action=successfulLogout&origin=${encodeURIComponent(origin)}${getQueryStringAddOn(extraQueryParamsForCallbacks)}`
|
|
43
|
+
const noLoginRedirectUrl = `${location.href.replace(/\?.*$/, '')}?action=unnecessaryLogout&origin=${encodeURIComponent(origin)}${getQueryStringAddOn(extraQueryParamsForCallbacks)}`
|
|
44
|
+
const accessToken = getAccessToken(origin)
|
|
45
|
+
|
|
46
|
+
const queryString = `redirectUrl=${encodeURIComponent(redirectUrl)}&noLoginRedirectUrl=${encodeURIComponent(noLoginRedirectUrl)}&accessToken=${encodeURIComponent(accessToken)}`
|
|
47
|
+
|
|
48
|
+
window.location = `${origin}/log-out?${queryString}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const init = ({ defaultOrigin, callbacks }) => {
|
|
52
|
+
|
|
53
|
+
if(defaultOrigin) setDefaultOrigin(defaultOrigin)
|
|
54
|
+
|
|
55
|
+
const getParam = regex => decodeURIComponent((window.location.search.match(regex) || [])[1] || '') || null
|
|
56
|
+
|
|
57
|
+
const origin = getParam(/[?&]origin=([^&]*)/)
|
|
58
|
+
const accessToken = getParam(/[?&]accessToken=([^&]*)/)
|
|
59
|
+
const action = getParam(/[?&]action=([^&]*)/)
|
|
60
|
+
const errorMessage = getParam(/[?&]errorMessage=([^&]*)/)
|
|
61
|
+
|
|
62
|
+
if(origin) {
|
|
63
|
+
const sessionSyncAuthAccessTokenByOrigin = JSON.parse(localStorage.getItem('sessionSyncAuthAccessTokenByOrigin') || `{}`)
|
|
64
|
+
if(accessToken) {
|
|
65
|
+
sessionSyncAuthAccessTokenByOrigin[origin] = accessToken
|
|
66
|
+
} else if([ 'successfulLogout', 'unnecessaryLogout' ].includes(action)) {
|
|
67
|
+
delete sessionSyncAuthAccessTokenByOrigin[origin]
|
|
68
|
+
}
|
|
69
|
+
localStorage.setItem('sessionSyncAuthAccessTokenByOrigin', JSON.stringify(sessionSyncAuthAccessTokenByOrigin))
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
callbacks[action] && callbacks[action]({ origin, accessToken, errorMessage })
|
|
74
|
+
} catch(err) {
|
|
75
|
+
console.error(err)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if(origin || accessToken || action || errorMessage) {
|
|
79
|
+
window.history.replaceState({}, null, `${location.href.replace(/\?.*$/, '')}${window.location.search.replace(/&?(?:origin|accessToken|action|errorMessage)=(?:[^&]*)/g, '').replace(/^\?$/, '')}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const sessionSyncAuth = {
|
|
85
|
+
getAccessToken,
|
|
86
|
+
logIn,
|
|
87
|
+
getUser,
|
|
88
|
+
logOut,
|
|
89
|
+
init,
|
|
90
|
+
setDefaultOrigin,
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
Object.freeze(sessionSyncAuth)
|
|
94
|
+
|
|
95
|
+
Object.defineProperty(window, 'sessionSyncAuth', {
|
|
96
|
+
value: sessionSyncAuth,
|
|
97
|
+
writable : false,
|
|
98
|
+
enumerable : true,
|
|
99
|
+
configurable : false
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
})();
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
const mysql = require('mysql')
|
|
2
|
+
const SqlString = require('mysql/lib/protocol/SqlString')
|
|
3
|
+
const { ConnectionString } = require('connection-string')
|
|
4
|
+
const util = require('util')
|
|
5
|
+
|
|
6
|
+
const setUpConnection = ({
|
|
7
|
+
connectionStr=`mysql://root@localhost/SessionSyncAuthSite`,
|
|
8
|
+
connectionObj,
|
|
9
|
+
}={}) => {
|
|
10
|
+
|
|
11
|
+
connectionObj = connectionObj || new ConnectionString(connectionStr)
|
|
12
|
+
|
|
13
|
+
global.sessionSyncAuthSiteConnection = mysql.createConnection({
|
|
14
|
+
multipleStatements: true,
|
|
15
|
+
dateStrings: true,
|
|
16
|
+
charset : 'utf8mb4',
|
|
17
|
+
queryFormat: function (query, values) {
|
|
18
|
+
if(!values) return query
|
|
19
|
+
|
|
20
|
+
if(/\:(\w+)/.test(query)) {
|
|
21
|
+
return query.replace(/\:(\w+)/g, (txt, key) => {
|
|
22
|
+
if(values.hasOwnProperty(key)) {
|
|
23
|
+
return this.escape(values[key])
|
|
24
|
+
}
|
|
25
|
+
return txt
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
} else {
|
|
29
|
+
return SqlString.format(query, values, this.config.stringifyObjects, this.config.timezone)
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
// debug: true,
|
|
33
|
+
host: connectionObj.host || connectionObj.hosts[0].name,
|
|
34
|
+
user: connectionObj.user || connectionObj.username,
|
|
35
|
+
password: connectionObj.password,
|
|
36
|
+
database: connectionObj.database || connectionObj.path[0],
|
|
37
|
+
port: connectionObj.port,
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
global.sessionSyncAuthSiteConnection.asyncQuery = util.promisify(global.sessionSyncAuthSiteConnection.query).bind(global.sessionSyncAuthSiteConnection)
|
|
41
|
+
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = setUpConnection
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
const jwt = require('jsonwebtoken')
|
|
2
|
+
|
|
3
|
+
const iso6393To1 = require('./iso639-3To1')
|
|
4
|
+
|
|
5
|
+
const setUpSessionSyncAuthRoutes = ({
|
|
6
|
+
app,
|
|
7
|
+
siteId,
|
|
8
|
+
authDomain,
|
|
9
|
+
jwtSecret,
|
|
10
|
+
protocol='https',
|
|
11
|
+
paths: {
|
|
12
|
+
getUser='/get-user',
|
|
13
|
+
logIn='/log-in',
|
|
14
|
+
logOut='/log-out',
|
|
15
|
+
authSync='/auth-sync',
|
|
16
|
+
}={},
|
|
17
|
+
languageColType='639-3', // OPTIONS: '639-1', '639-3', 'IETF'
|
|
18
|
+
}) => {
|
|
19
|
+
|
|
20
|
+
app.get(getUser, (req, res, next) => {
|
|
21
|
+
res.json({
|
|
22
|
+
status: req.user ? 'Logged in' : 'Not logged in',
|
|
23
|
+
user: req.user,
|
|
24
|
+
})
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
app.get(logIn, (req, res, next) => {
|
|
28
|
+
const { loggedInRedirectUrl, cancelRedirectUrl } = req.query
|
|
29
|
+
|
|
30
|
+
const jwtData = jwt.sign(
|
|
31
|
+
{
|
|
32
|
+
cancelRedirectUrl,
|
|
33
|
+
loggedInRedirectUrl,
|
|
34
|
+
},
|
|
35
|
+
jwtSecret,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
res.redirect(`${protocol}://${authDomain}/login?siteId=${encodeURIComponent(siteId)}&jwtData=${encodeURIComponent(jwtData)}`)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
app.get(logOut, (req, res, next) => {
|
|
42
|
+
const { redirectUrl, noLoginRedirectUrl } = req.query
|
|
43
|
+
|
|
44
|
+
if(!req.user) {
|
|
45
|
+
console.warn(`logout attemped with no login`)
|
|
46
|
+
return res.redirect(noLoginRedirectUrl || redirectUrl)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const jwtData = jwt.sign(
|
|
50
|
+
{
|
|
51
|
+
userId: req.user.id,
|
|
52
|
+
redirectUrl,
|
|
53
|
+
},
|
|
54
|
+
jwtSecret,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
res.redirect(`${protocol}://${authDomain}/logout?siteId=${encodeURIComponent(siteId)}&jwtData=${encodeURIComponent(jwtData)}`)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
app.post(authSync, async (req, res, next) => {
|
|
61
|
+
const { payload } = req.body
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
|
|
65
|
+
if(!global.sessionSyncAuthSiteConnection) {
|
|
66
|
+
throw new Error('You must include the authenticate() middleware prior to calling setUpSessionSyncAuthRoutes.')
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
userTableName,
|
|
71
|
+
sessionTableName,
|
|
72
|
+
userTableColNameMap,
|
|
73
|
+
sessionTableColNameMap,
|
|
74
|
+
} = req.sessionSyncAuthSiteOptions
|
|
75
|
+
|
|
76
|
+
const { users } = jwt.verify(payload, jwtSecret)
|
|
77
|
+
|
|
78
|
+
if(!users) throw "Invalid payload. Missing `payload.user`."
|
|
79
|
+
if(!(users instanceof Array)) throw "Invalid payload. `payload.users` must be an array."
|
|
80
|
+
|
|
81
|
+
const queries = []
|
|
82
|
+
const variables = {}
|
|
83
|
+
|
|
84
|
+
const getSnakeCaseOfKeys = (obj, allowedKeys) => {
|
|
85
|
+
const newObj = {}
|
|
86
|
+
Object.keys(obj).forEach(key => {
|
|
87
|
+
const snakeCaseKey = key.replace(/[A-Z]/g, val => `_${val.toLowerCase()}`)
|
|
88
|
+
if(allowedKeys.includes(snakeCaseKey)) {
|
|
89
|
+
newObj[snakeCaseKey] = obj[key]
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
return newObj
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const mapKeys = (obj, table) => {
|
|
96
|
+
let colNameMap = {}
|
|
97
|
+
if(table === 'user') colNameMap = userTableColNameMap
|
|
98
|
+
if(table === 'session') colNameMap = sessionTableColNameMap
|
|
99
|
+
|
|
100
|
+
for(let defaultCol in colNameMap) {
|
|
101
|
+
obj[colNameMap[defaultCol]] = obj[defaultCol]
|
|
102
|
+
delete obj[defaultCol]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const convertMsTimestampsToDateTimeStrings = obj => {
|
|
107
|
+
Object.keys(obj).forEach(key => {
|
|
108
|
+
if(/At$/.test(key)) {
|
|
109
|
+
obj[key] = new Date(obj[key]).toISOString().replace(/T/, ' ').replace(/Z/, '')
|
|
110
|
+
}
|
|
111
|
+
})
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
users.forEach(user => {
|
|
115
|
+
const { id, sessions, createdAt, updatedAt } = user
|
|
116
|
+
|
|
117
|
+
if(!id) throw "Invalid payload. Item in `payload.users` missing `id` key."
|
|
118
|
+
if(!sessions) throw "Invalid payload. Item in `payload.users` missing `sessions` key."
|
|
119
|
+
if(!(sessions instanceof Array)) throw "Invalid payload. `payload.users[].sessions` must be an array."
|
|
120
|
+
if(!createdAt) throw "Invalid payload. Item in `payload.users` missing `createdAt` key."
|
|
121
|
+
if(!updatedAt) throw "Invalid payload. Item in `payload.users` missing `updatedAt` key."
|
|
122
|
+
|
|
123
|
+
convertMsTimestampsToDateTimeStrings(user)
|
|
124
|
+
|
|
125
|
+
// add/update the user
|
|
126
|
+
|
|
127
|
+
variables[`user__${id}`] = getSnakeCaseOfKeys(
|
|
128
|
+
user,
|
|
129
|
+
[ 'id', 'email', 'name', 'image', 'language', 'created_at', 'updated_at' ],
|
|
130
|
+
)
|
|
131
|
+
|
|
132
|
+
if(languageColType !== 'IETF') {
|
|
133
|
+
variables[`user__${id}`].language = variables[`user__${id}`].language.split('-')[0]
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if(languageColType === '639-1') {
|
|
137
|
+
variables[`user__${id}`].language = iso6393To1[variables[`user__${id}`].language] || 'en'
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
mapKeys(variables[`user__${id}`], 'user')
|
|
141
|
+
|
|
142
|
+
queries.push(`
|
|
143
|
+
INSERT INTO \`${userTableName}\` SET :user__${id}
|
|
144
|
+
ON DUPLICATE KEY UPDATE :user__${id}
|
|
145
|
+
`)
|
|
146
|
+
|
|
147
|
+
// add the sessions
|
|
148
|
+
|
|
149
|
+
sessions.forEach(session => {
|
|
150
|
+
const { accessToken, createdAt } = session
|
|
151
|
+
|
|
152
|
+
if(!accessToken) throw "Invalid payload. Item in `payload.users[].sessions` missing `accessToken` key."
|
|
153
|
+
if(!createdAt) throw "Invalid payload. Item in `payload.users[].sessions` missing `createdAt` key."
|
|
154
|
+
|
|
155
|
+
convertMsTimestampsToDateTimeStrings(session)
|
|
156
|
+
|
|
157
|
+
variables[`session__${accessToken}`] = {
|
|
158
|
+
...getSnakeCaseOfKeys(
|
|
159
|
+
session,
|
|
160
|
+
[ 'created_at', 'access_token' ],
|
|
161
|
+
),
|
|
162
|
+
user_id: id,
|
|
163
|
+
}
|
|
164
|
+
mapKeys(variables[`session__${accessToken}`], 'session')
|
|
165
|
+
|
|
166
|
+
queries.push(`
|
|
167
|
+
INSERT IGNORE INTO \`${sessionTableName}\` SET :session__${accessToken}
|
|
168
|
+
`)
|
|
169
|
+
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
// delete old sessions
|
|
173
|
+
|
|
174
|
+
variables[`userId__${id}`] = id
|
|
175
|
+
variables[`accessTokens__${id}`] = [
|
|
176
|
+
'dummy_token', // so the SQL is still valid even if there are no sessions
|
|
177
|
+
...sessions.map(({ accessToken }) => accessToken),
|
|
178
|
+
]
|
|
179
|
+
|
|
180
|
+
queries.push(`
|
|
181
|
+
DELETE FROM \`${sessionTableName}\`
|
|
182
|
+
WHERE \`${(sessionTableColNameMap.user_id || `user_id`).replace(/`/g, '')}\` = :userId__${id}
|
|
183
|
+
AND \`${(sessionTableColNameMap.access_token || `access_token`).replace(/`/g, '')}\` NOT IN (:accessTokens__${id})
|
|
184
|
+
`)
|
|
185
|
+
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
await global.sessionSyncAuthSiteConnection.asyncQuery(
|
|
189
|
+
queries.join(';'),
|
|
190
|
+
variables,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
res.json({ success: true })
|
|
194
|
+
|
|
195
|
+
} catch(err) {
|
|
196
|
+
console.log('Bad call to sync_api_endpoint', err)
|
|
197
|
+
res.status(400).send(`Invalid payload.`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
module.exports = setUpSessionSyncAuthRoutes
|
package/test/app.html
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
|
|
3
|
+
<html>
|
|
4
|
+
|
|
5
|
+
<head>
|
|
6
|
+
<script src="/sessionSyncAuthFrontend.js"></script>
|
|
7
|
+
|
|
8
|
+
<script>
|
|
9
|
+
|
|
10
|
+
window.sessionSyncAuth.init({
|
|
11
|
+
defaultOrigin: 'http://localhost:3002',
|
|
12
|
+
callbacks: {
|
|
13
|
+
canceledLogin: ({ origin }) => alert(`Canceled login to ${origin}`),
|
|
14
|
+
successfulLogin: ({ origin }) => alert(`Successful login to ${origin}`),
|
|
15
|
+
successfulLogout: ({ origin }) => alert(`Successful logout from ${origin}`),
|
|
16
|
+
unnecessaryLogout: ({ origin }) => alert(`Unnecessary logout from ${origin}`),
|
|
17
|
+
error: ({ errorMessage }) => alert(`ERROR: ${errorMessage}`),
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
const showAccessTokens = () => {
|
|
22
|
+
alert(localStorage.getItem('sessionSyncAuthAccessTokenByOrigin'))
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getUser = async options => {
|
|
26
|
+
const json = await window.sessionSyncAuth.getUser(options)
|
|
27
|
+
console.log(json)
|
|
28
|
+
alert(JSON.stringify(json, true))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
</script>
|
|
32
|
+
</head>
|
|
33
|
+
|
|
34
|
+
<body>
|
|
35
|
+
|
|
36
|
+
<div>
|
|
37
|
+
<button
|
|
38
|
+
onclick="javascript:showAccessTokens();"
|
|
39
|
+
>
|
|
40
|
+
Show local access tokens
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div>
|
|
45
|
+
<button
|
|
46
|
+
onclick="javascript:window.sessionSyncAuth.logIn();"
|
|
47
|
+
>
|
|
48
|
+
Log in on 3002
|
|
49
|
+
</button>
|
|
50
|
+
<button
|
|
51
|
+
onclick="javascript:getUser();"
|
|
52
|
+
>
|
|
53
|
+
Get user on 3002
|
|
54
|
+
</button>
|
|
55
|
+
<button
|
|
56
|
+
onclick="javascript:window.sessionSyncAuth.logOut();"
|
|
57
|
+
>
|
|
58
|
+
Log out on 3002
|
|
59
|
+
</button>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div>
|
|
63
|
+
<button
|
|
64
|
+
onclick="javascript:window.sessionSyncAuth.logIn({ origin: 'http://localhost:3003' });"
|
|
65
|
+
>
|
|
66
|
+
Log in on 3003
|
|
67
|
+
</button>
|
|
68
|
+
<button
|
|
69
|
+
onclick="javascript:getUser({ origin: 'http://localhost:3003' });"
|
|
70
|
+
>
|
|
71
|
+
Get user on 3003
|
|
72
|
+
</button>
|
|
73
|
+
<button
|
|
74
|
+
onclick="javascript:window.sessionSyncAuth.logOut({ origin: 'http://localhost:3003' });"
|
|
75
|
+
>
|
|
76
|
+
Log out on 3003
|
|
77
|
+
</button>
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
</body>
|
|
81
|
+
|
|
82
|
+
</html>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const express = require('express')
|
|
4
|
+
const app = express()
|
|
5
|
+
const cors = require('cors')
|
|
6
|
+
const bodyParser = require('body-parser')
|
|
7
|
+
|
|
8
|
+
const authenticate = require('../src/authenticate')
|
|
9
|
+
const setUpSessionSyncAuthRoutes = require('../src/setUpSessionSyncAuthRoutes')
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
|
|
13
|
+
const [
|
|
14
|
+
port,
|
|
15
|
+
dbPrefix,
|
|
16
|
+
authDomain,
|
|
17
|
+
x,
|
|
18
|
+
] = process.argv.slice(2)
|
|
19
|
+
|
|
20
|
+
if(port === undefined) {
|
|
21
|
+
throw new Error(`NO_PARAMS`)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if(x !== undefined) {
|
|
25
|
+
throw new Error(`BAD_PARAMS`)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Middleware
|
|
29
|
+
|
|
30
|
+
app.use(cors())
|
|
31
|
+
|
|
32
|
+
if(dbPrefix) {
|
|
33
|
+
app.use(bodyParser.json())
|
|
34
|
+
app.use(authenticate({
|
|
35
|
+
// connectionObj or connectionStr required for all but localhost testing
|
|
36
|
+
userTableName: `${dbPrefix}_users`,
|
|
37
|
+
sessionTableName: `${dbPrefix}_sessions`,
|
|
38
|
+
}))
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// API
|
|
42
|
+
|
|
43
|
+
if(!dbPrefix) {
|
|
44
|
+
|
|
45
|
+
app.get('/', (req, res, next) => {
|
|
46
|
+
res.sendFile('app.html', {root: __dirname })
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
app.get('/sessionSyncAuthFrontend.js', (req, res, next) => {
|
|
50
|
+
res.sendFile('sessionSyncAuthFrontend.js', {root: `${__dirname}/../src` })
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if(dbPrefix) {
|
|
56
|
+
setUpSessionSyncAuthRoutes({
|
|
57
|
+
app,
|
|
58
|
+
siteId: port,
|
|
59
|
+
authDomain: authDomain || `localhost:3005`,
|
|
60
|
+
jwtSecret: `secret:${port}`,
|
|
61
|
+
protocol: authDomain ? `https` : `http`,
|
|
62
|
+
languageColType: '639-1',
|
|
63
|
+
})
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Error handler
|
|
67
|
+
app.use((err, req, res, next) => {
|
|
68
|
+
console.error(err)
|
|
69
|
+
res.status(500).send('Internal Serverless Error')
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
// Local listener
|
|
73
|
+
app.listen(port, (err) => {
|
|
74
|
+
if (err) throw err
|
|
75
|
+
console.log(`> Ready on http://localhost:${port}`)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
} catch(err) {
|
|
79
|
+
|
|
80
|
+
const logSyntax = () => {
|
|
81
|
+
console.log(`Syntax: \`npm run dev [port] [dbPrefix] [authDomain]\`\n`)
|
|
82
|
+
console.log(`Example #1: \`npm run 3002 db1\``)
|
|
83
|
+
console.log(`Example #2: \`npm run 3003 db2 auth.staging.resourcingeducation.com\``)
|
|
84
|
+
console.log(``)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
switch(err.message.split(',')[0]) {
|
|
88
|
+
|
|
89
|
+
case `NO_PARAMS`: {
|
|
90
|
+
logSyntax()
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
case `BAD_PARAMS`: {
|
|
95
|
+
console.log(`\n-----------------------`)
|
|
96
|
+
console.log(`ERROR: Bad parameters.`)
|
|
97
|
+
console.log(`-----------------------\n`)
|
|
98
|
+
logSyntax()
|
|
99
|
+
break
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
default: {
|
|
103
|
+
console.log(`\n-----------------------`)
|
|
104
|
+
console.log(`\nERROR: ${err.message}\n`)
|
|
105
|
+
console.log(`-----------------------\n`)
|
|
106
|
+
logSyntax()
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
process.exit()
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = app
|