-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch '746-apis-to-create-user-and-group' into dev
- Loading branch information
Showing
45 changed files
with
3,003 additions
and
1,719 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
# Authenticated APIs | ||
|
||
We have recently launched our MVP of APIs to be used by partners. Currently you have to contact us at hello@hylo.com if you want access to the APIs. We will give you an API key and secret manually. | ||
|
||
# API calls | ||
|
||
### Authenticate | ||
Before making any API calls you must get an auth token | ||
|
||
`POST to https://hylo.com/noo/oauth/token` | ||
|
||
__Headers:__ | ||
Content-Type: application/x-www-form-urlencoded | ||
|
||
__Parameters (all required):__ | ||
- grant_type = client_credentials | ||
- resource = the server URL you are making the call to (e.g. https://hylo.com, https://staging.hylo.com, or https://localhost:3000) | ||
- scope = api:write | ||
- client_id = YOUR_ID | ||
- client_secret = YOUR_SECRET | ||
|
||
This call will return an Auth Token for use in later API calls. This token will expire in 2 hours at which point you will need to make another API call to get a new Auth Token (AUTH_TOKEN). | ||
|
||
For every subsequent API you will need to authorize by passing this token as Bearer Token in the Authorization Header: | ||
`Authorization: Bearer AUTH_TOKEN` | ||
|
||
### Create a User | ||
|
||
`POST to https://hylo.com/noo/user` | ||
|
||
__Headers:__ | ||
Content-Type: application/x-www-form-urlencoded | ||
|
||
__Parameters:__ | ||
- name (required) = Judy Mangrove | ||
- email (required) = email@email.com | ||
- groupId (optional) = the id of a group to add the user to | ||
|
||
__Return value__: | ||
|
||
On success this will return a JSON object that looks like: | ||
``` | ||
{ | ||
"id": "44692", | ||
"name": "Judy Mangrove", | ||
"email": "email@email.com" | ||
} | ||
``` | ||
|
||
If there is already a user with this email registered you will receive: | ||
`{ "message": "User already exists" }` | ||
|
||
|
||
### Create a Group | ||
|
||
`POST to https://hylo.com/noo/graphql` | ||
|
||
__Headers:__ | ||
Content-Type: application/json | ||
|
||
This is a GraphQL based endpoint so you will want the pass in a raw POST data | ||
Example GraphQL mutation: | ||
``` | ||
{ | ||
"query": "mutation ($data: GroupInput, $asUserId: ID) { createGroup(data: $data, asUserId: $asUserId) { id name slug } }", | ||
"variables": { | ||
"data": { | ||
"accessibility": 1, | ||
"name": "Test Group", | ||
"slug": "unique-url-slug", | ||
"parentIds": [], | ||
"visibility": 1, | ||
"groupExtensions": [ | ||
{ | ||
"type": "farm-onboarding", | ||
"data": { | ||
"farm_email": "test@farm.org" | ||
} | ||
} | ||
] | ||
}, | ||
"asUserId": USER_ID | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,113 +1,63 @@ | ||
import jwt from 'jsonwebtoken' | ||
import InvitationService from '../services/InvitationService' | ||
import OIDCAdapter from '../services/oidc/KnexAdapter' | ||
|
||
module.exports = { | ||
create: function (req, res) { | ||
const { name, email, email_validated, password } = req.allParams() | ||
|
||
return User.create({name, email: email ? email.toLowerCase() : null, email_validated, account: {type: 'password', password}}) | ||
.then(async (user) => { | ||
await Analytics.trackSignup(user.id, req) | ||
if (req.param('login')) { | ||
await UserSession.login(req, user, 'password') | ||
} | ||
await user.refresh() | ||
|
||
if (req.param('resp') === 'user') { | ||
return res.ok({ | ||
name: user.get('name'), | ||
email: user.get('email') | ||
}) | ||
} else { | ||
return res.ok({}) | ||
} | ||
}) | ||
.catch(function (err) { | ||
res.status(422).send({ error: err.message ? err.message : err }) | ||
}) | ||
}, | ||
create: async function (req, res) { | ||
const { name, email, groupId } = req.allParams() | ||
const group = groupId && await Group.find(groupId) | ||
|
||
status: function (req, res) { | ||
res.ok({signedIn: UserSession.isLoggedIn(req)}) | ||
}, | ||
|
||
sendEmailVerification: async function (req, res) { | ||
const email = req.param('email') | ||
|
||
const user = await User.find(email) | ||
let user = await User.find(email, {}, false) | ||
if (user) { | ||
return res.status(422).send({ error: "duplicate-email" }) | ||
} | ||
|
||
const code = await UserVerificationCode.create(email) | ||
const token = jwt.sign({ | ||
iss: 'https://hylo.com', | ||
aud: 'https://hylo.com', | ||
sub: email, | ||
exp: Math.floor(Date.now() / 1000) + (60 * 60 * 4), // 4 hour expiration | ||
code: code.get('code') | ||
}, process.env.JWT_SECRET); | ||
|
||
Queue.classMethod('Email', 'sendEmailVerification', { | ||
email, | ||
templateData: { | ||
code: code.get('code'), | ||
verify_url: Frontend.Route.verifyEmail(token) | ||
// User already exists | ||
if (group) { | ||
if (!(await GroupMembership.hasActiveMembership(user, group))) { | ||
// If user exists but is not part of the group then invite them | ||
let message = `${req.api_client} is excited to invite you to join our community on Hylo.` | ||
let subject = `Join me in ${group.get('name')} on Hylo!` | ||
if (req.api_client) { | ||
const client = await (new OIDCAdapter("Client")).find(req.api_client.id) | ||
if (!client) { | ||
return res.status(403).json({ error: 'Unauthorized' }) | ||
} | ||
subject = client.invite_subject || `You've been invited to join ${group.get('name')} on Hylo` | ||
message = client.invite_message || `Hi ${user.get('name')}, <br><br> We're excited to welcome you into our community. Click below to join ${group.get('name')} on Hylo.` | ||
} | ||
const inviteBy = await group.moderators().fetchOne() | ||
|
||
await InvitationService.create({ | ||
sessionUserId: inviteBy?.id, | ||
groupId: group.id, | ||
userIds: [user.id], | ||
message, | ||
subject | ||
}) | ||
} | ||
} | ||
}) | ||
|
||
return res.ok({}) | ||
}, | ||
return res.ok({ message: "User already exists" }) | ||
} | ||
|
||
sendPasswordReset: function (req, res) { | ||
const email = req.param('email') | ||
return User.query(q => q.whereRaw('lower(email) = ?', email.toLowerCase())).fetch().then(function (user) { | ||
if (!user) { | ||
return res.ok({}) | ||
} else { | ||
const nextUrl = req.param('evo') | ||
? Frontend.Route.evo.passwordSetting() | ||
: null | ||
const token = user.generateJWT() | ||
Queue.classMethod('Email', 'sendPasswordReset', { | ||
email: user.get('email'), | ||
return User.create({name, email: email ? email.toLowerCase() : null, email_validated: false, active: false, group }) | ||
.then(async (user) => { | ||
Queue.classMethod('Email', 'sendFinishRegistration', { | ||
email, | ||
templateData: { | ||
login_url: Frontend.Route.jwtLogin(user, token, nextUrl) | ||
api_client: req.api_client?.name, | ||
group_name: group && group.get('name'), | ||
version: 'with link', | ||
verify_url: Frontend.Route.verifyEmail(email, user.generateJWT()) | ||
} | ||
}) | ||
return res.ok({}) | ||
} | ||
}) | ||
.catch(res.serverError.bind(res)) | ||
}, | ||
|
||
verifyEmailByCode: async function (req, res) { | ||
let { code, email } = req.allParams() | ||
|
||
if (await UserVerificationCode.verify(email, code)) { | ||
// Store verified email for 4 hours | ||
res.cookie('verifiedEmail', email, { maxAge: 1000 * 60 * 60 * 4 }); | ||
return res.ok(email) | ||
} | ||
|
||
return res.status(403).json({ error: 'invalid code' }); | ||
}, | ||
|
||
verifyEmailByToken: async function (req, res) { | ||
let { token } = req.allParams() | ||
const verify = Promise.promisify(jwt.verify, jwt) | ||
try { | ||
const decoded = await jwt.verify(token, process.env.JWT_SECRET, { audience: 'https://hylo.com', issuer: 'https://hylo.com' }) | ||
const email = decoded.sub | ||
const code = decoded.code | ||
|
||
if (await UserVerificationCode.verify(email, code)) { | ||
// Store verified email for 4 hours | ||
res.cookie('verifiedEmail', email, { maxAge: 1000 * 60 * 60 * 4 }); | ||
return res.redirect(Frontend.Route.signupFinish()) | ||
} | ||
} catch (e) {} | ||
|
||
return res.redirect(Frontend.Route.signup('invalid-link')) | ||
return res.ok({ | ||
id: user.id, | ||
name: user.get('name'), | ||
email: user.get('email') | ||
}) | ||
}) | ||
.catch(function (err) { | ||
res.status(422).send({ error: err.message ? err.message : err }) | ||
}) | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.