Skip to content

Commit 2f16d84

Browse files
committed
Fix issues with database; make it easier to test
These changes fix compatibility issues with common SQL databases including MySQL, MariaDB and Postgres. * Fixes nextauthjs#147 - datetime now ANSI SQL timestamp * Fixes nextauthjs#160 - AccessToken and RefreshToken type change from varchar to text * Adds Docker Compose files to make it easier to test database integration. TODO: * Update documentation with configuration examples and latest compatibility info * Create DB URI parser (currently only object config works) * Database table/collection name prefix (will default to `next-auth_`) * MongoDB support MongoDB has some issues which mean it will require additional work and refactoring to support (while preserving SQL DB support, which is important). It's going to take some thinking about to get right; MongoDB support might have to be dropped from 2.0 (and follow in a subsequent release) but I'm going to review options and consider the impact before making a call.
1 parent 74b334f commit 2f16d84

14 files changed

+149
-59
lines changed

client.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
module.exports = require('./dist/client').default
1+
module.exports = require('./dist/client').default

package-lock.json

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "next-auth",
3-
"version": "2.0.0-beta.44",
3+
"version": "2.0.0-beta.45",
44
"description": "An authentication library for Next.js",
55
"repository": "https://github.com/iaincollins/next-auth.git",
66
"author": "Iain Collins <[email protected]>",
@@ -17,7 +17,9 @@
1717
"publish:beta": "npm publish --tag beta",
1818
"publish:canary": "npm publish --tag canary",
1919
"lint": "standard",
20-
"lint:fix": "standard --fix"
20+
"lint:fix": "standard --fix",
21+
"test:db:start": "docker-compose -f tests/docker/docker-compose.yml up",
22+
"test:db:stop": "docker-compose -f tests/docker/docker-compose.yml down"
2123
},
2224
"files": [
2325
"dist",

src/adapters/typeorm/index.js

+45-20
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,31 @@ const Adapter = (config, options = {}) => {
1919
const EmailVerification = options.EmailVerification ? options.EmailVerification.model : Models.EmailVerification.model
2020
const EmailVerificationSchema = options.EmailVerification ? options.EmailVerification.schema : Models.EmailVerification.schema
2121

22+
// Models default to being suitable for ANSI SQL database
23+
// Some flexiblity is required to support non-SQL databases
24+
const idKey = 'id'
25+
const getById = (id) => id
26+
27+
/* @TODO This is a work in progress
28+
// Some custom logic is required to make schemas compatible with MongoDB
29+
if (config.type === 'mongodb') {
30+
if (!options.mongodb) throw new Error('Experimental feature')
31+
32+
idKey = 'id'
33+
AccountSchema.columns.id.objectId = true
34+
AccountSchema.columns.userId.objectId = true
35+
UserSchema.columns.id.objectId = true
36+
SessionSchema.columns.id.objectId = true
37+
SessionSchema.columns.userId.objectId = true
38+
EmailVerificationSchema.columns.id.objectId = true
39+
40+
getById = (id) => {
41+
console.log('fancy getById', id)
42+
return config.mongodb.ObjectId(id)
43+
}
44+
}
45+
*/
46+
2247
// Parse config (uses options)
2348
const defaultConfig = {
2449
name: 'default',
@@ -71,12 +96,12 @@ const Adapter = (config, options = {}) => {
7196
// Display debug output if debug option enabled
7297
function _debug (...args) {
7398
if (appOptions.debug) {
74-
console.log('[DEBUG]', ...args)
99+
console.log('[NextAuth.js][DEBUG]', ...args)
75100
}
76101
}
77102

78103
async function createUser (profile) {
79-
_debug('Create user', profile)
104+
_debug('createUser', profile)
80105
try {
81106
// Create user account
82107
const user = new User(profile.name, profile.email, profile.image)
@@ -88,17 +113,17 @@ const Adapter = (config, options = {}) => {
88113
}
89114

90115
async function getUser (id) {
91-
_debug('Get user', id)
116+
_debug('getUser', id)
92117
try {
93-
return connection.getRepository(User).findOne({ id })
118+
return connection.getRepository(User).findOne({ [idKey]: getById(id) })
94119
} catch (error) {
95120
console.error('GET_USER_BY_ID_ERROR', error)
96121
return Promise.reject(new Error('GET_USER_BY_ID_ERROR', error))
97122
}
98123
}
99124

100125
async function getUserByEmail (email) {
101-
_debug('Get user by email address', email)
126+
_debug('getUserByEmail', email)
102127
try {
103128
return connection.getRepository(User).findOne({ email })
104129
} catch (error) {
@@ -108,37 +133,37 @@ const Adapter = (config, options = {}) => {
108133
}
109134

110135
async function getUserByProviderAccountId (providerId, providerAccountId) {
111-
_debug('Get user by provider account ID', providerId, providerAccountId)
136+
_debug('getUserByProviderAccountId', providerId, providerAccountId)
112137
try {
113138
const account = await connection.getRepository(Account).findOne({ providerId, providerAccountId })
114139
if (!account) { return null }
115-
return connection.getRepository(User).findOne({ id: account.userId })
140+
return connection.getRepository(User).findOne({ [idKey]: getById(account.userId) })
116141
} catch (error) {
117142
console.error('GET_USER_BY_PROVIDER_ACCOUNT_ID_ERROR', error)
118143
return Promise.reject(new Error('GET_USER_BY_PROVIDER_ACCOUNT_ID_ERROR', error))
119144
}
120145
}
121146

122147
async function getUserByCredentials (credentials) {
123-
_debug('Get user by credentials', credentials)
148+
_debug('getUserByCredentials', credentials)
124149
// @TODO Get user from DB
125150
return false
126151
}
127152

128153
async function updateUser (user) {
129-
_debug('Update user', user)
154+
_debug('updateUser', user)
130155
// @TODO Save changes to user object in DB
131156
return false
132157
}
133158

134159
async function deleteUser (userId) {
135-
_debug('Delete user', userId)
160+
_debug('deleteUser', userId)
136161
// @TODO Delete user from DB
137162
return false
138163
}
139164

140165
async function linkAccount (userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires) {
141-
_debug('Link provider account', userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires)
166+
_debug('linkAccount', userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires)
142167
try {
143168
// Create provider account linked to user
144169
const account = new Account(userId, providerId, providerType, providerAccountId, refreshToken, accessToken, accessTokenExpires)
@@ -150,15 +175,15 @@ const Adapter = (config, options = {}) => {
150175
}
151176

152177
async function unlinkAccount (userId, providerId, providerAccountId) {
153-
_debug('Unlink provider account', userId, providerId, providerAccountId)
178+
_debug('unlinkAccount', userId, providerId, providerAccountId)
154179
// @TODO Get current user from DB
155180
// @TODO Delete [provider] object from user object
156181
// @TODO Save changes to user object in DB
157182
return false
158183
}
159184

160185
async function createSession (user) {
161-
_debug('Create session', user)
186+
_debug('createSession', user)
162187
try {
163188
const { sessionMaxAge } = appOptions
164189
let expires = null
@@ -178,7 +203,7 @@ const Adapter = (config, options = {}) => {
178203
}
179204

180205
async function getSession (sessionToken) {
181-
_debug('Get session', sessionToken)
206+
_debug('getSession', sessionToken)
182207
try {
183208
const session = await connection.getRepository(Session).findOne({ sessionToken })
184209

@@ -196,7 +221,7 @@ const Adapter = (config, options = {}) => {
196221
}
197222

198223
async function updateSession (session, force) {
199-
_debug('Update session', session)
224+
_debug('updateSession', session)
200225
try {
201226
const { sessionMaxAge, sessionUpdateAge } = appOptions
202227

@@ -216,7 +241,7 @@ const Adapter = (config, options = {}) => {
216241
if (new Date() > dateSessionIsDueToBeUpdated) {
217242
const newExpiryDate = new Date()
218243
newExpiryDate.setTime(newExpiryDate.getTime() + sessionMaxAge)
219-
session.sessionExpires = newExpiryDate.toISOString()
244+
session.sessionExpires = newExpiryDate
220245
} else if (!force) {
221246
return null
222247
}
@@ -234,7 +259,7 @@ const Adapter = (config, options = {}) => {
234259
}
235260

236261
async function deleteSession (sessionToken) {
237-
_debug('Delete session', sessionToken)
262+
_debug('deleteSession', sessionToken)
238263
try {
239264
return await connection.getRepository(Session).delete({ sessionToken })
240265
} catch (error) {
@@ -244,7 +269,7 @@ const Adapter = (config, options = {}) => {
244269
}
245270

246271
async function createEmailVerification (email, url, token, secret, provider) {
247-
_debug('Create verification request', email)
272+
_debug('createEmailVerification', email)
248273
try {
249274
const { site, verificationMaxAge } = appOptions
250275
const { verificationCallback } = provider
@@ -277,7 +302,7 @@ const Adapter = (config, options = {}) => {
277302
}
278303

279304
async function getEmailVerification (email, token, secret, provider) {
280-
_debug('Get verification request', email, token)
305+
_debug('getEmailVerification', email, token)
281306
try {
282307
// Hash token provided with secret before trying to match it with datbase
283308
// @TODO Use bcrypt function here instead of simple salted hash
@@ -298,7 +323,7 @@ const Adapter = (config, options = {}) => {
298323
}
299324

300325
async function deleteEmailVerification (email, token, secret, provider) {
301-
_debug('Delete verification request', email, token)
326+
_debug('deleteEmailVerification', email, token)
302327
try {
303328
// Delete email verification so it cannot be used again
304329
const hashedToken = createHash('sha256').update(`${token}${secret}`).digest('hex')

src/adapters/typeorm/models/account.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export const AccountSchema = {
3535
unique: true
3636
},
3737
userId: {
38-
type: 'varchar'
38+
type: 'int'
3939
},
4040
providerId: {
4141
type: 'varchar'
@@ -47,14 +47,15 @@ export const AccountSchema = {
4747
type: 'varchar'
4848
},
4949
refreshToken: {
50-
type: 'varchar',
50+
type: 'text',
5151
nullable: true
5252
},
5353
accessToken: {
54-
type: 'varchar'
54+
type: 'text'
5555
},
56+
// @TODO AccessToken expiry is not yet tracked (varies across providers)
5657
accessTokenExpires: {
57-
type: 'varchar',
58+
type: 'timestamp',
5859
nullable: true
5960
}
6061
}

src/adapters/typeorm/models/email-verification.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export const EmailVerificationSchema = {
2323
unique: true
2424
},
2525
expires: {
26-
type: 'date'
26+
type: 'timestamp'
2727
}
2828
}
2929
}

src/adapters/typeorm/models/session.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export const SessionSchema = {
2626
unique: true
2727
},
2828
sessionExpires: {
29-
type: 'date'
29+
type: 'timestamp'
3030
},
3131
accessToken: {
3232
type: 'varchar',

src/server/routes/callback.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export default async (req, res, options, done) => {
4848
// If is missing email address (NB: the only field on a profile currently required)
4949
res.status(302).setHeader('Location', `${baseUrl}/error?error=EmailRequired`)
5050
} else {
51-
console.error('OAUTH_CALLBACK_ERROR', error)
51+
console.error('OAUTH_CALLBACK_HANDLER_ERROR', error)
5252
res.status(302).setHeader('Location', `${baseUrl}/error?error=Callback`)
5353
}
5454
res.end()

tests/docker/docker-compose.yml

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Start Mongo, MySQL and Postgres databases
2+
# Though other databases will be supported, these are the initial targets.
3+
# Uses Docker Compose v2 as v3 doesn't support extends
4+
version: '2'
5+
6+
services:
7+
8+
mongo:
9+
extends:
10+
file: mongo.yml
11+
service: mongo
12+
13+
mysql:
14+
extends:
15+
file: mysql.yml
16+
service: mysql
17+
18+
postgres:
19+
extends:
20+
file: postgres.yml
21+
service: postgres

tests/docker/mongo.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '2'
2+
3+
services:
4+
5+
mongo:
6+
image: bitnami/mongodb
7+
restart: always
8+
environment:
9+
MONGODB_USERNAME: nextauth
10+
MONGODB_PASSWORD: password
11+
MONGODB_DATABASE: nextauth
12+
ports:
13+
- "27017:27017"

tests/docker/mysql.yml

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
version: '2'
2+
3+
services:
4+
5+
mysql:
6+
image: mysql
7+
command: --default-authentication-plugin=mysql_native_password
8+
restart: always
9+
environment:
10+
MYSQL_USER: nextauth
11+
MYSQL_PASSWORD: password
12+
MYSQL_DATABASE: nextauth
13+
MYSQL_RANDOM_ROOT_PASSWORD: 'yes'
14+
ports:
15+
- "3306:3306"

tests/docker/postgres.yml

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
version: '2'
2+
3+
services:
4+
5+
postgres:
6+
image: postgres
7+
restart: always
8+
environment:
9+
POSTGRES_USER: nextauth
10+
POSTGRES_PASSWORD: password
11+
POSTGRES_DB: nextauth
12+
ports:
13+
- "5432:5432"

www/sidebars.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ module.exports = {
66
'providers',
77
'adapters',
88
'client',
9-
'rest-api',
9+
'rest-api'
1010
],
1111
v1: [
1212
'v1/getting-started-v1',

0 commit comments

Comments
 (0)