mojio-js-sdk
Version:
Mojio javascript REST client.
340 lines (314 loc) • 18 kB
text/coffeescript
# version 4.0.0
MojioModelSDK = require './MojioModelSDK'
# The authentication segment of the Mojio SDK. Authentication is accomplished through the use of a Mojio OAuth server.
# Consumer applications authenticate by redirecting to the OAuth server and waiting for a redirect back to the application's
# authentication return url. Once the authentication server has validated the user's credentials it passes back a token
# through the redirect url to the consumer application.
#
# For server applications that have their own username and password within the Mojio API, the OAuth server provides a means
# to directly authenticate the server with it's credentials with a direct client-server request.
#
# @example
# mojioAuthSdk = new MojioSDK({sdk: MojioAuthSDK}) # instantiate the mojioSDK to do only authentication methods.
#
module.exports = class MojioAuthSDK extends MojioModelSDK
defaults = {
accountsURL: 'accounts.moj.io'
}
styleParameters = ['callback', 'promise', 'sync', 'subscribe', 'observable', 'async']
token = null
# Construct a MojioAuthSDK object.
#
# @example New MojioAuthSDK
# Mojio = new MojioAuthSDK({ version: 'v2' })
# @param [object] options Configurable options for the sdk.
# @option options [String] version api version to use 'v1' or 'v2't
# @return {object} Returns a new MojioAuthSDK object
# @nodoc
constructor: (options={}) ->
super()
@configure(options, defaults)
@currentUser = null
# set up the state variables needed for the Auth SDK
@stateMachine.client = @client_id
@stateMachine.secret = @client_secret
@stateMachine.site = @site
@stateMachine.tokenPath = @tokenPath
@stateMachine.authorizationPath = @authorizationPath
setup = (parameters, match, name) =>
if typeof parameters is 'object'
for property,value of parameters
if (property in match)
eval("this."+property)(value)
else
throw new Error "Parameter not used in "+name+" flow: "+property
(authorizeParameters = ['username', 'password', 'credentials', 'scope', 'email'])
.push styleParameters...
# A method that authorizes access to a user's data. There are two ways to authorize users,
# depending on whether the application is designed to be used by a consumer, or it is designed
# as a server that users trust to act on their behalf. Generally, this devides into two camps,
# client applications for consumers or server applications for enterprise applications. For
# client applications, the user is redirected to mojio's authorization server which will collect
# the user's password outside the application so that the user can give permission to or revoke
# permission from the application to use their data. For enterprise or server side applications,
# the user's password is known to the application and can be sent directly to the authorization
# server for verification. In both cases, the authorization server will return a token that will
# be used to access the user's data.
#
# @param {object} authorization An object that contains the information needed to authorize a user.
#
# @example Client application for consumer authorization. In the node js express environment, redirects
# to the authorization server within a browser and returns a token in a document.
# app.get('/authCode', (req, res) ->
# # step 1 of authorization code workflow.
# sdk
# .authorize(redirect_uri)
# .scope(['full'])
# .redirect(res)
# )
# @example Browser based implicit flow authorization. Redirects the user to auth server.
# sdk
# .authorize(redirect_uri)
# .scope(['full'])
# .redirect( { redirect: (url) -> window.location = url } )
# @return {object} this
authorize: (redirect_url, implicit = null) ->
# authorization_code or implicit flows (server or browser for end users/consumers.
# {client_id:, response_type:, redirect_url:, scope:, realm:}, {type=token, user=, password=}
if !implicit?
implicit = document?
setup(redirect_url, authorizeParameters, 'authorize')
@stateMachine.setMethod("POST")
@stateMachine.setEndpoint("accounts")
@stateMachine.setResource("oauth2")
@stateMachine.setAction("authorize")
if (@sdk_env == 'browser' or implicit)
@stateMachine.setBody({response_type: 'token', redirect_uri: redirect_url, client_id: @client_id})
else if (@sdk_env == 'nodejs')
@stateMachine.setBody({response_type: 'code', redirect_uri: redirect_url, client_id: @client_id})
return @
(unauthorizeParameters = ['login', 'consent', 'loginAndConsent', 'prompt', 'parse', 'code'])
.push styleParameters...
# A method that un-authorizes access to a user's data, removing grants to data. Parameters to
# this function
# @param {string} redirect_url where to redirect if the user logs in again using the
# oauth page retrieved as a result of the prompt specified.
# @example Log the user out, but keep permissions for this application
# sdk.unauthorize("http://localhost:3000/callback").login().callback(...)
# @example Log the user out, and deny permissions for this application
# sdk.unauthorize("http://localhost:3000/callback").login().consent().callback(...)
# @return {object} this
unauthorize: (redirect_url, implicit = null) ->
if (document? and !implict?)
implicit = true
else
implicit = false
setup(redirect_url, unauthorizeParameters, 'unauthorize')
# authorization_code or implicit flows (server or browser for end users/consumers.
# {client_id:, response_type:, redirect_url:, scope:, realm:}, {type=token, user=, password=}
@stateMachine.setMethod("POST")
@stateMachine.setEndpoint("accounts")
@stateMachine.setResource("oauth2")
@stateMachine.setAction("authorize")
if (@sdk_env == 'browser' or implicit)
@stateMachine.setBody({response_type: 'token', redirect_uri: redirect_url, client_id: @client_id})
else if (@sdk_env == 'nodejs')
@stateMachine.setBody({response_type: 'code', redirect_uri: redirect_url, client_id: @client_id})
return @
(tokenParameters= [])
.push authorizeParameters...
# A method that returns an authorization token after authorization has returned a code. Used in
# combination with 'parse', 'refresh', and 'password' to implement completion of an authorization
# workflow. Parse is used in conjunction with code and implicit flows, call authorize first, then
# after the redirect call token().parse(response). Password with token implements the 'password'
# authorization flow for server based authorization where the server is also the owner of the
# resources (the user account that will be accessing the vehicles, mojios, and trips for that user
# account and no one else). This would be token().password('username', 'password') and is done without
# a prior 'authorize' call. Refresh is used to refresh already active tokens, giving them an
# extended expiration timespan, token().refresh().
#
# @param redirect_url {string} The response from the authorization workflow.
#
# @example Get the token after returning from a consumer application's redirect to the authorization server
# sdk.token().parse(document.location.hash.match(/access_token=([0-9a-f-]{36})/)) )
# @example Get the token after returning from an implicit flow
# sdk.token().parse(response).callback(...)
# @example Refresh the internal sdk stored token
# sdk.token().refresh().callback(...)
# @example Refresh the an arbitrary token
# sdk.token().refresh(some_token_object).callback(...)
# @return {object} this
token: (redirect_url=null) ->
if (redirect_url?)
if !setup(redirect_url, tokenParameters, 'token')
redirect_uri = redirect_url
else
redirect_uri = @stateMachine.getBody().redirect_uri
@stateMachine.setMethod("POST")
@stateMachine.setEndpoint("accounts")
@stateMachine.setResource("oauth2")
@stateMachine.setAction("token")
@stateMachine.setBody({ client_id: @client_id, client_secret: @client_secret })
@stateMachine.setBody({ redirect_uri: redirect_uri}) if redirect_uri?
return @
# second half of authorization code flow, or parse of the return from the implicit flow
#
# @param refresh_token {object} The authorization-token object's refresh token returned from the authorization/token workflow.
# the authorization token returned from the authorization workflow is an object that has several fields, one of
# which is labeled "refresh_token". All of this is cached in the sdk, but you can pass a valid refresh token in, a
# in a new token will be returned.
#
# @example Get the token after returning from a consumer application's redirect to the authorization server
# sdk.token().parse(document.location.hash.match(/access_token=([0-9a-f-]{36})/)) )
# @example Get the token after returning from an implicit flow
# sdk.token().parse(response).callback(...)
# @return {object} this
parse: (return_url) ->
if (return_url? and return_url.query? and return_url.query.code?)
code = return_url.query.code
@stateMachine.setBody({
code: code,
grant_type: 'authorization_code'
})
@stateMachine.setCallback((error, result) =>
if (error)
console.log('Access Token Error', JSON.stringify(error.content)+" message:"+error.statusMessage)
else
# recover the token
@stateMachine.setToken(result)
)
else if return_url.location?
if (return_url.location.hash== "")
@stateMachine.setAnswer("")
else if (return_url.location.hash?)
obj = {}
obj[t.split("=")[0]] = t.split("=")[1] for t in return_url.location.hash.split("#")[1].split("&")
@stateMachine.setToken(obj)
@stateMachine.setAnswer(obj)
else if (typeof return_url is 'object' and return_url.access_token?)
@stateMachine.setToken(return_url)
@stateMachine.setAnswer(return_url)
else if (typeof return_url is 'string')
return_url = { access_token: return_url, expires_in: "unknown", referesh_token: "unknown", token_type: "bearer" }
@stateMachine.setToken(return_url)
@stateMachine.setAnswer(return_url)
return @
# A method that refreshes an authorization token, gives it more active time. Actually, a new token is returned
# when a refresh call is made.
#
# @param refresh_token {object} The authorization-token object's refresh token returned from the authorization/token workflow.
# the authorization token returned from the authorization workflow is an object that has several fields, one of
# which is labeled "refresh_token". All of this is cached in the sdk, but you can pass a valid refresh token in, a
# in a new token will be returned.
#
# @example Get the token after returning from a consumer application's redirect to the authorization server
# sdk.token("http://localhost:3000/callback").refresh().callback(...)
# @return {object} this
refresh: (refresh_token) ->
@stateMachine.setBody({ refresh_token: refresh_token, grant_type: 'refresh_token' })
return @
# A method that specifies that when unauthorize is initiated, the user should be logged out of
# the application. The application will still have permission to access the user's resources
# when they log in again with 'authorize'.
# @example Log the user out and do not deny permissions, go to the oauth2 login prompt
# sdk.unauthorize("http://localhost:3000/callback").login().callback(...)
# @return {object} this
login: () ->
@prompt ({prompt: 'login'})
return @
# A method that specifies that when unauthorize is initiated, the application will no longer have
# access to the user's resources.
# @example Deny permissions and go to the oauth2 consent prompt.
# sdk.unauthorize("http://localhost:3000/callback").loginAndConsent().callback(...)
# @return {object} this
consent: () ->
@prompt ({prompt: 'consent'})
return @
# A method that specifies that when unauthorize is initiated, the user should be logged out. The
# application will also be denied permission to access the user's resources when they log in again
# with 'authorize' unless permission is given by the user again.
# @example Log the user out and deny permissions. Go through the oauth2 login and consent prompts.
# sdk.unauthorize("http://localhost:3000/callback").loginAndConsent().callback(...)
# @return {object} this
loginAndConsent: () ->
@prompt ({prompt: 'consent,login'})
return @
# A helper method to set the body of the REST uri for login and consent calls. Can be used
# instead of login, consent, or loginAndConsent calls
# @example Set the unauthorize chain to 'login' and 'consent'
# sdk.unauthorize("http://localhost:3000/callback").prompt({prompt: 'consent, login'}).callback(...)
# sdk.unauthorize("http://localhost:3000/callback").prompt('consent, login').callback(...)
# sdk.unauthorize("http://localhost:3000/callback").prompt(['consent', 'login']).callback(...)
# sdk.unauthorize("http://localhost:3000/callback").prompt('login').callback(...)
# @param prompt {object, array, or string} object: {prompt: 'login,consent'}, array: ['login','consent'], string:'login,consent'.
# @return {object} this
prompt: (prompt) ->
if ((@stateMachine.getBody().prompt?) and
((@stateMachine.getBody().prompt is 'login' and prompt.prompt is 'consent' or prompt is 'consent') or
(@stateMachine.getBody().prompt is 'consent' and prompt.prompt is 'login' or prompt is 'login')))
@stateMachine.setBody ({prompt: 'consent,login'})
else if (typeof prompt is 'string')
@stateMachine.setBody ({prompt: prompt})
else if (prompt instanceof Array )
@stateMachine.setBody ({prompt: prompt.join()})
# or as is.
else
@stateMachine.setBody (prompt)
return @
# Set the scope of the authorization workflow. The user will be asked for consent of the given 'scope'
# for the application to have access to their resources.
# @param scopes {array or string} Array of scopes, or a space separated list of scopes in a string.
# @return {object} this
scope: (scopes) ->
# @validator.validateScope(scopes, @scopes)
if (typeof scopes is 'string')
param = scopes.replace(/,/g,' ') # remove commas if given.
@stateMachine.setBody({scope: param })
else
param = ''
scopes.map (scope) -> param+=scope+' '
@stateMachine.setBody({ scope: param.slice(0,-1) })
return @
# Set the username for a server side, resource owner 'password' authorization workflow.
# @param username {string} The username to use for the password authorization.
# @return {object} this
username: (username) ->
@stateMachine.setBody({ username: username})
return @
# Set the email for a server side, resource owner 'password' authorization workflow.
# @param email {string} The email to use for the password authorization.
# @return {object} this
email: (email) ->
@stateMachine.setBody({ username: email})
return @
# Set the password for a server side, resource owner 'password' authorization workflow.
# @param password {string} The password to use for the password authorization.
# @return {object} this
password: (password) ->
@stateMachine.setBody({ password: password })
return @
# This call is used to specify both username and password for an authorization workflow.
# @param usernameOrEmail_or_credentials {string or object} In the case of a string, this is the
# username. In the case of an object, it's both username and password given in the following
# format: {username: '', password: ''}
# @param password {string} The password if username and password aren't given in the first parameter.
# @return {object} this
credentials: (usernameOrEmail_or_credentials, password=null) ->
if (typeof usernameOrEmail_or_credentials is 'object')
credentials=usernameOrEmail_or_credentials
else
credentials={ username: usernameOrEmail_or_credentials, password: password }
@stateMachine.validator.credentials(credentials)
credentials['grant_type'] = 'password'
@stateMachine.setBody(credentials)
return @
# Synonym for the credentials() call.
# @param username_or_credentials {string or object} In the case of a string, this is the
# username. In the case of an object, it's both username and password given in the following
# format: {username: '', password: ''}
# @param password {string} The password if username and password aren't given in the first parameter.
# @return {object} this
with: (usernameOrEmail_or_credentials, password=null) ->
return @credentials(usernameOrEmail_or_credentials, password)
getToken: () ->
return @stateMachine.getToken()