Skip to content

Commit

Permalink
Add database adapter and models
Browse files Browse the repository at this point in the history
* Uses typeorm as works with a large number of data stores.
* Compatible with common SQL, document storage & lightweight databases.
* Adapter logic integrated into signup flow but not yet complete.
  • Loading branch information
iaincollins committed May 17, 2020
1 parent 3dad0cc commit 4bf1339
Show file tree
Hide file tree
Showing 11 changed files with 1,100 additions and 21 deletions.
761 changes: 761 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
},
"license": "ISC",
"dependencies": {
"oauth": "^0.9.15"
"oauth": "^0.9.15",
"typeorm": "^0.2.24"
}
}
1 change: 0 additions & 1 deletion src/adapters/elasticsearch.js

This file was deleted.

208 changes: 208 additions & 0 deletions src/adapters/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
import 'reflect-metadata'
import { createConnection, getConnection, getManager, EntitySchema } from 'typeorm'

import { Account, AccountSchema } from '../models/account'
import { User, UserSchema } from '../models/user'

const Default = (config) => {

function debug(...args) {
if (process.env.NODE_ENV === 'development')
console.log(...args)
}

const defaultConfig = {
name: 'default',
autoLoadEntities: true,
entities: [
new EntitySchema(AccountSchema),
new EntitySchema(UserSchema)
],
synchronize: true,
logging: false,
}

config = {
...defaultConfig,
...config
}

let connection = null

async function getAdapter() {

// Helper function to reuse / restablish connections
// (useful if they drop when after being idle)
async function getDatabaseConnection() {
return new Promise(async resolve => {
// Get current connection by name
connection = getConnection(config.name)

// If connection is no longer established, reconnect
if (!connection.isConnected)
connection = await connection.connect()

resolve()
})
}

if (!connection) {
// If no connection, create new connection
try {
connection = await createConnection(config)
} catch (error) {
if (error.name === "AlreadyHasActiveConnectionError") {
// If creating connection fails because it's already
// been re-established, check it's really up
await getDatabaseConnection()
} else {
console.error('ADAPTER_CONNECTION_ERROR', error)
}
}
} else {
// If the connection object already exists, ensure it's valid
await getDatabaseConnection()
}

// Called when a user signs in
async function createUser(profile) {
debug('Create user account', profile)
return new Promise(async (resolve, reject) => {
try {
// Create user account
const user = new User(profile.name, profile.email, profile.image)
await getManager().save(user)
resolve(user)
} catch (error) {
// Reject if fails
console.error('CREATE_USER_ERROR', error)
reject(new Error('CREATE_USER_ERROR', error))
}
})
}

async function updateUser(user) {
debug('Update user account', user)
return new Promise((resolve, reject) => {
// @TODO Save changes to user object in DB
resolve(true)
})
}

async function getUserById(id) {
debug('Get user account by ID', id)
return new Promise((resolve, reject) => {
// @TODO Get user from DB
resolve(false)
})
}

async function getUserByProviderAccountId(provider, providerAccountId) {
debug('Get user account by provider account ID', provider, providerAccountId)
return new Promise((resolve, reject) => {
// @TODO Get user from DB
resolve(false)
})
}

async function getUserByEmail(email) {
debug('Get user account by email address', email)
return new Promise((resolve, reject) => {
// @TODO Get user from DB
resolve(false)
})
}

async function getUserByCredentials(credentials) {
debug('Get user account by credentials', credentials)
return new Promise((resolve, reject) => {
// @TODO Get user from DB
resolve(true)
})
}


async function deleteUserById(userId) {
debug('Delete user account', userId)
return new Promise((resolve, reject) => {
// @TODO Delete user from DB
resolve(true)
})
}

async function linkAccount(userId, providerId, providerAccountId, refreshToken, accessToken, accessTokenExpires) {
debug('Link provider account', userId, providerId, providerAccountId, refreshToken, accessToken, accessTokenExpires)
return new Promise(async (resolve, reject) => {
try {
// Create user account
const account = new Account(userId, providerId, providerAccountId, refreshToken, accessToken, accessTokenExpires)
await getManager().save(account)
resolve(account)
} catch (error) {
// Reject if fails
console.error('LINK_ACCOUNT_ERROR', error)
reject(new Error('LINK_ACCOUNT_ERROR', error))
}
})
}

async function unlinkAccount(userId, providerId, providerAccountId) {
debug('Unlink provider account', userId, providerId, providerAccountId)
return new Promise((resolve, reject) => {
// @TODO Get current user from DB
// @TODO Delete [provider] object from user object
// @TODO Save changes to user object in DB
resolve(true)
})
}

async function createSession(user) {
debug('Create session for user', user)
return new Promise((resolve, reject) => {
// @TODO Create session
resolve(true)
})
}

async function getSessionById(sessionId) {
debug('Get session by ID', sessionId)
return new Promise((resolve, reject) => {
// @TODO Get session
resolve(true)
})
}

async function deleteSessionById(sessionId) {
debug('Delete session by ID', sessionId)
return new Promise((resolve, reject) => {
// @TODO Delete session
resolve(true)
})
}

return Promise.resolve({
createUser,
updateUser,
getUserById,
getUserByProviderAccountId,
getUserByEmail,
getUserByCredentials,
deleteUserById,
linkAccount,
unlinkAccount,
createSession,
getSessionById,
deleteSessionById
})
}

return {
getAdapter
}
}

export default {
Default
}


1 change: 0 additions & 1 deletion src/adapters/mongodb.js

This file was deleted.

1 change: 0 additions & 1 deletion src/adapters/mysql.js

This file was deleted.

52 changes: 52 additions & 0 deletions src/models/account.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { createHash } from 'crypto'

export class Account {
constructor(
userId,
providerId,
providerAccountId,
refreshToken,
accessToken,
accessTokenExpires
) {
this.id = createHash('sha256').update(`${providerId}:${providerAccountId}`).digest('hex')
this.userId = userId
this.providerId = providerId
this.providerAccountId = providerAccountId
this.refreshToken = refreshToken
this.accessToken = accessToken
this.accessTokenExpires = accessTokenExpires
}
}

export const AccountSchema = {
name: 'Account',
target: Account,
columns: {
id: {
primary: true,
type: 'varchar',
unique: true
},
userId: {
type: 'varchar'
},
providerId: {
type: 'varchar'
},
providerAccountId: {
type: 'varchar'
},
refreshToken: {
type: 'varchar',
nullable: true
},
accessToken: {
type: 'varchar'
},
accessTokenExpires: {
type: 'varchar',
nullable: true
}
}
}
37 changes: 37 additions & 0 deletions src/models/user.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export class User {
constructor(name, email, image) {
this.name = name
this.email = email
this.image = image
}
}

export const UserSchema = {
name: 'User',
target: User,
columns: {
id: {
primary: true,
type: 'int',
generated: true
},
name: {
type: 'varchar',
nullable: true
},
email: {
type: 'varchar',
unique: true
},
image: {
type: 'varchar',
nullable: true
}
},
relations: {
accounts: {
target: 'Account',
type: 'one-to-many'
}
}
}
15 changes: 9 additions & 6 deletions src/server/lib/oauth/callback.js
Original file line number Diff line number Diff line change
Expand Up @@ -109,17 +109,20 @@ function _getProfile(error, profileData, accessToken, refreshToken, provider) {
}
}

// Return "clean" profile and raw profile object
// Return profile, raw profile and auth provider details
return ({
profile: {
name: profile.name,
email: profile.email,
image: profile.image,
[provider.id]: {
id: profile.id,
accessToken,
refreshToken,
}
},
account: {
provider: provider.id,
type: provider.type,
id: profile.id,
refreshToken,
accessToken,
accessTokenExpires: null
},
_profile: profileData
})
Expand Down
Loading

0 comments on commit 4bf1339

Please sign in to comment.