Skip to content

Commit

Permalink
feat(auth): add custom payload from social authenication to auth plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
Frantz Kati committed Mar 1, 2021
1 parent 396f280 commit 44a8f3e
Show file tree
Hide file tree
Showing 21 changed files with 328 additions and 173 deletions.
131 changes: 53 additions & 78 deletions examples/blog/index.js
Original file line number Diff line number Diff line change
@@ -1,100 +1,75 @@
require('dotenv').config()
const { cms } = require('@tensei/cms')
const { auth } = require('@tensei/auth')
const { rest } = require('@tensei/rest')
const { media } = require('@tensei/media')
const { graphql } = require('@tensei/graphql')
const { tensei, welcome, select, text, resource, slug, boolean, belongsTo, timestamp, hasMany } = require('@tensei/core')
const { mailgun } = require('@tensei/mail')
const {
tensei,
welcome,
resource,
text,
textarea,
dateTime,
slug,
hasMany,
belongsTo,
route
} = require('@tensei/core')

tensei()
.root(__dirname)
.mailer('transactions')
.routes([
route('Send mail via mailgun')
.get()
.path('/mailgun')
.handle(async ({ mailer }, response) => {
const result = await mailer.send(message => {
message
.from('no-reply@sandbox.mgsend.net', 'Emma at Mgsend')
.to('bahdcoder@gmail.com', 'Frantz Kati')
.subject(`Welcome to Crypto Hippo, Emily`)
.htmlView('mails/mailgun', {
user: 'Emily Myers'
})
})

response.json([])
})
])
.resources([
resource('Track')
.hideOnInsertApi()
.hideOnUpdateApi()
.hideOnDeleteApi()
.fields([
text('Name'),
hasMany('Assignment'),
slug('Slug').from('Name'),
boolean('Open To Enrolments').default(true),
]).displayField('Name'),
resource('Enrolment')
.hideOnInsertApi()
.hideOnDeleteApi()
// .filters([
// filter('Owner')
// .fields([
// text('Slug'),
// boolean('Published')
// ])
// .condition(fields => ({ user: { id: { $eq: fields.slug } } }))
// .default()
// ])
.fields([
belongsTo('User'),
belongsTo('Track'),
]),
resource('Invite')
.hideOnInsertApi()
.hideOnUpdateApi()
.hideOnDeleteApi()
resource('Post')
.fields([
text('Email').rules('email', 'required'),
belongsTo('Track'),
timestamp('Expires At'),
]).displayField('Email'),
resource('Assignment')
.hideOnInsertApi()
.hideOnUpdateApi()
.hideOnDeleteApi()
.fields([
text('Repository Prefix').notNullable(),
text('Title'),
text('Description'),
belongsTo('Track'),
hasMany('Submission'),
text('Template Repository'),
text('Pull Request Template'),
boolean('Initial Pull Request'),
]).displayField('Title'),
resource('Submission')
.hideOnInsertApi()
.hideOnUpdateApi()
.hideOnDeleteApi()
slug('Slug').from('Title'),
textarea('Description'),
textarea('Content'),
dateTime('Published At'),
belongsTo('Category')
])
.displayField('Title'),
resource('Category')
.fields([
belongsTo('User'),
belongsTo('Track'),
belongsTo('Assignment'),
text('Repository'),
boolean('Completed'),
belongsTo('User', 'reviewer').label('Reviewer')
text('Name').notNullable().rules('required'),
textarea('Description'),
hasMany('Post')
])
.displayField('Repository')
.displayField('Name')
])
.plugins([
welcome(),
mailgun('transactions').domain(process.env.MAILGUN_DOMAIN).plugin(),
cms().plugin(),
auth().rolesAndPermissions().plugin(),
media().plugin(),
auth()
.configureTokens({
accessTokenExpiresIn: 60 * 60 * 60 * 24
})
.setup(({ user }) => {
user.fields([
text('Avatar').nullable(),
select('Role').options(['Student', 'Reviewer']).nullable()
])
}).social('github', {
key: process.env.GITHUB_API_KEY,
secret: process.env.GITHUB_API_SECRET,
scope: ['user', 'repo'],
clientCallback: 'http://localhost:3000/auth'
}).plugin(),
rest().plugin(),
graphql().plugin()
])
.register(({ getQuery }) => {
getQuery('enrolments')
.authorize((ctx) => !!ctx.user && ctx.body?.where?.user?.id?._eq === ctx.user.id.toString())
.databaseConfig({
type: 'sqlite',
debug: true,
dbName: 'tensei'
})
.start()
.catch(console.error)
3 changes: 2 additions & 1 deletion examples/blog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@
"@tensei/graphql": "^0.7.12",
"@tensei/mde": "^0.7.12",
"@tensei/media": "^0.7.12",
"@tensei/social-auth": "^0.7.12"
"@tensei/social-auth": "^0.7.12",
"marked": "^2.0.1"
},
"scripts": {
"example:dev": "nodemon src/index"
Expand Down
1 change: 1 addition & 0 deletions packages/auth/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export interface AuthPluginConfig {
accessTokenExpiresIn: number
refreshTokenExpiresIn: number
}
getUserPayloadFromProviderData?: (providerData: DataPayload) => DataPayload
separateSocialLoginAndRegister: boolean
beforeLogin: AuthHookFunction
afterLogin: AuthHookFunction
Expand Down
34 changes: 29 additions & 5 deletions packages/auth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class Auth {
verifyEmails: false,
skipWelcomeEmail: false,
rolesAndPermissions: false,
providers: {}
providers: {},
}

private TwoFactorAuth: any = null
Expand Down Expand Up @@ -566,7 +566,8 @@ class Auth {
orm: config.orm,
authConfig: this.config,
resourcesMap: this.resources,
apiPath: this.config.apiPath
apiPath: this.config.apiPath,
getUserPayloadFromProviderData: this.config.getUserPayloadFromProviderData
})
}

Expand Down Expand Up @@ -1065,11 +1066,11 @@ class Auth {
]
: []),
...(this.socialAuthEnabled()
? [
? this.config.separateSocialLoginAndRegister ? [
route(`Social Auth Login`)
.path(this.getApiPath('social/login'))
.post()
.id(this.getRouteId(`social_login_${name}`))
.id('social_login')
.extend({
docs: {
...extend,
Expand All @@ -1084,7 +1085,7 @@ class Auth {
),
route(`Social Auth Register`)
.path(this.getApiPath('social/register'))
.id(this.getRouteId(`social_register_${name}`))
.id('social_register')
.post()
.extend({
docs: {
Expand All @@ -1101,6 +1102,23 @@ class Auth {
)
)
)
] : [
route(`Social Auth Confirm`)
.path(this.getApiPath(`social/confirm`))
.id('social_confirm')
.post()
.extend({
docs: {
...extend,
summary: `Confirm a ${name} (login or register) via a social provider.`,
description: `This operation requires an access_token gotten after a redirect from the social provider.`
}
})
.handle(async (request, response) => response.formatter.ok(
await this.socialAuth(
request as any,
)
))
]
: []),
...(this.config.enableRefreshTokens
Expand Down Expand Up @@ -1797,6 +1815,12 @@ class Auth {
throw ctx.userInputError('Invalid email verification token.')
}

public getUserPayloadFromProviderData(getUserPayloadFromProviderData: AuthPluginConfig['getUserPayloadFromProviderData']) {
this.config.getUserPayloadFromProviderData = getUserPayloadFromProviderData

return this
}

private socialAuth = async (
ctx: ApiContext,
action?: 'login' | 'register'
Expand Down
21 changes: 20 additions & 1 deletion packages/cms/plugin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ class CmsPlugin {
])
})
.boot(async config => {
const { app, orm, extendEvents, currentCtx } = config
const { app, orm, extendEvents, resources, currentCtx } = config
const Store = ExpressSessionMikroORMStore(
ExpressSession,
this.sessionMikroOrmOptions
Expand All @@ -548,6 +548,25 @@ class CmsPlugin {

this.router.use(this.setAuth)

this.router.use((request, response, next) => {
// set filter parameters
resources.forEach(resource => {
resource.data.filters.forEach(filter => {
const filterFromBody = request.body.filters?.find(
(bodyFitler: any) =>
bodyFitler.name === filter.config.shortName
)

request.manager.setFilterParams(
filter.config.shortName,
filterFromBody?.args || {}
)
})
})

next()
})

this.router.use(Csurf())
;[...getRoutes(config, this.config), ...this.routes()].forEach(
route => {
Expand Down
10 changes: 9 additions & 1 deletion packages/common/src/fields/Field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -565,8 +565,16 @@ export class Field implements FieldContract {
return this
}

public virtual<T extends FieldContract>(this: T): T {
public virtual<T extends FieldContract>(this: T, compute: (value: any) => any): T {
this.property.persist = false
this.property.getter = true
this.property.getterName = `get_${this.databaseField}`
this.property.virtualGetter = compute

this.hideOnIndex()
this.hideOnUpdate()
this.hideOnCreate()
this.nullable()

return this
}
Expand Down
1 change: 1 addition & 0 deletions packages/common/src/fields/Textarea.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export class Textarea extends Text {
super(name, databaseField)

this.property.columnTypes = ['text']
this.hideOnIndex()
}

alwaysShow() {
Expand Down
5 changes: 2 additions & 3 deletions packages/common/src/filters/Filter.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { camelCase } from 'change-case'
import { FilterQuery, Dictionary } from '@mikro-orm/core'
import { snakeCase } from 'change-case'
import { FieldContract } from '@tensei/common'
import {
FilterCondition,
Expand All @@ -17,7 +16,7 @@ export class Filter<T = any> implements FilterContract {
fields: []
}

constructor(name: string, shortName = camelCase(name)) {
constructor(name: string, shortName = snakeCase(name)) {
this.config.name = name
this.config.shortName = shortName
}
Expand Down
2 changes: 1 addition & 1 deletion packages/common/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export { oneToMany, OneToMany, hasMany } from './fields/OneToMany'
export { manyToOne, ManyToOne, belongsTo } from './fields/ManyToOne'
export { manyToMany, ManyToMany, belongsToMany } from './fields/ManyToMany'

export { filter } from './filters/Filter'
export { filter, Filter } from './filters/Filter'

export { ResourceHelpers } from './helpers'
export { card, Card } from './dashboard/Card'
Expand Down
3 changes: 2 additions & 1 deletion packages/common/typings/fields.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ declare module '@tensei/common/fields' {
serializedName?: string
comment?: string
userDefined?: boolean
virtualGetter?: (value: any) => any
}

interface SerializedField {
Expand Down Expand Up @@ -196,7 +197,7 @@ declare module '@tensei/common/fields' {
isRelationshipField: boolean
onUpdate(hook: () => any): this
onCreate(hook: () => any): this
shadow(): this
virtual(compute: (value: any) => any): this
removeFromSidebarOnForms(): this
dockToSidebarOnForms(): this
formComponent(component: string): this
Expand Down
8 changes: 7 additions & 1 deletion packages/core/Tensei.ts
Original file line number Diff line number Diff line change
Expand Up @@ -332,7 +332,13 @@ export class Tensei implements TenseiContract {
const event = this.ctx.events[eventName]

event.config.listeners.forEach(listener => {
this.ctx.emitter.on(eventName as any, listener as any)
this.ctx.emitter.on(eventName as any, (data) => {
try {
listener(data as any)
} catch (error) {
this.ctx.logger.error(error)
}
})
})
})

Expand Down
6 changes: 6 additions & 0 deletions packages/core/database/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,12 @@ class Database {
)
)
})

// Generate entity class getters (for virtual properties)
resource.data.fields.filter(field => field.property.persist === false).forEach(field => {
entityClass.prototype[field.property.getterName] = field.property.virtualGetter
})

return entityClass
}

Expand Down
4 changes: 2 additions & 2 deletions packages/graphql/src/Resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Utils } from '@tensei/common'
import { Configuration } from '@mikro-orm/core'
import { Configuration, EntityManager } from '@mikro-orm/core'
import { parseResolveInfo } from 'graphql-parse-resolve-info'
import {
GraphQlQueryContract,
Expand Down Expand Up @@ -119,7 +119,7 @@ export const getResolvers = (
})
}

const data = ctx.manager.create(
const data: any = ctx.manager.create(
resource.data.pascalCaseName,
payload
)
Expand Down
Loading

0 comments on commit 44a8f3e

Please sign in to comment.