|  | 
|  | 1 | +/** | 
|  | 2 | + * Module dependencies | 
|  | 3 | + */ | 
|  | 4 | + | 
|  | 5 | +var AnvilConnect = require('anvil-connect-nodejs') | 
|  | 6 | +var UnauthorizedError = AnvilConnect.UnauthorizedError | 
|  | 7 | +var debug = require('debug')('ldnode:auth-oidc') | 
|  | 8 | + | 
|  | 9 | +/** | 
|  | 10 | + * Constructor | 
|  | 11 | + */ | 
|  | 12 | + | 
|  | 13 | +function AnvilConnectExpress (options) { | 
|  | 14 | +  options = options || {} | 
|  | 15 | +  this.client = new AnvilConnect(options) | 
|  | 16 | +  this.respond = options.respond || true | 
|  | 17 | +} | 
|  | 18 | + | 
|  | 19 | +/** | 
|  | 20 | + * Next | 
|  | 21 | + */ | 
|  | 22 | + | 
|  | 23 | +function handler (req, res, next) { | 
|  | 24 | +  var self = this | 
|  | 25 | + | 
|  | 26 | +  return function (err) { | 
|  | 27 | +    var statusCode = err.statusCode || 400 | 
|  | 28 | + | 
|  | 29 | +    if (self.respond) { | 
|  | 30 | +      res.status(statusCode).json({ | 
|  | 31 | +        error: err.error, | 
|  | 32 | +        error_description: err.error_description | 
|  | 33 | +      }) | 
|  | 34 | +    } else { | 
|  | 35 | +      next(err) | 
|  | 36 | +    } | 
|  | 37 | +  } | 
|  | 38 | +} | 
|  | 39 | + | 
|  | 40 | +AnvilConnectExpress.prototype.handler = handler | 
|  | 41 | + | 
|  | 42 | +/** | 
|  | 43 | + * Verify Middleware | 
|  | 44 | + */ | 
|  | 45 | + | 
|  | 46 | +function verifier (options) { | 
|  | 47 | +  var self = this | 
|  | 48 | + | 
|  | 49 | +  return function (req, res, next) { | 
|  | 50 | +    var accessToken | 
|  | 51 | +    var nexter = self.handler(req, res, next) | 
|  | 52 | + | 
|  | 53 | +    // Check for an access token in the Authorization header | 
|  | 54 | +    if (req.headers && req.headers.authorization) { | 
|  | 55 | +      var components = req.headers.authorization.split(' ') | 
|  | 56 | +      var scheme = components[0] | 
|  | 57 | +      var credentials = components[1] | 
|  | 58 | + | 
|  | 59 | +      if (components.length !== 2) { | 
|  | 60 | +        return nexter(new UnauthorizedError({ | 
|  | 61 | +          error: 'invalid_request', | 
|  | 62 | +          error_description: 'Invalid authorization header', | 
|  | 63 | +          statusCode: 400 | 
|  | 64 | +        })) | 
|  | 65 | +      } | 
|  | 66 | + | 
|  | 67 | +      if (scheme !== 'Bearer') { | 
|  | 68 | +        return nexter(new UnauthorizedError({ | 
|  | 69 | +          error: 'invalid_request', | 
|  | 70 | +          error_description: 'Invalid authorization scheme', | 
|  | 71 | +          statusCode: 400 | 
|  | 72 | +        })) | 
|  | 73 | +      } | 
|  | 74 | + | 
|  | 75 | +      accessToken = credentials | 
|  | 76 | +    } | 
|  | 77 | + | 
|  | 78 | +    // Check for an access token in the request URI | 
|  | 79 | +    if (req.query && req.query.access_token) { | 
|  | 80 | +      if (accessToken) { | 
|  | 81 | +        return nexter(new UnauthorizedError({ | 
|  | 82 | +          error: 'invalid_request', | 
|  | 83 | +          error_description: 'Multiple authentication methods', | 
|  | 84 | +          statusCode: 400 | 
|  | 85 | +        })) | 
|  | 86 | +      } | 
|  | 87 | + | 
|  | 88 | +      accessToken = req.query.access_token | 
|  | 89 | +    } | 
|  | 90 | + | 
|  | 91 | +    // Check for an access token in the request body | 
|  | 92 | +    if (req.body && req.body.access_token) { | 
|  | 93 | +      if (accessToken) { | 
|  | 94 | +        return nexter(new UnauthorizedError({ | 
|  | 95 | +          error: 'invalid_request', | 
|  | 96 | +          error_description: 'Multiple authentication methods', | 
|  | 97 | +          statusCode: 400 | 
|  | 98 | +        })) | 
|  | 99 | +      } | 
|  | 100 | + | 
|  | 101 | +      if (req.headers && | 
|  | 102 | +        req.headers['content-type'] !== 'application/x-www-form-urlencoded') { | 
|  | 103 | +        return nexter(new UnauthorizedError({ | 
|  | 104 | +          error: 'invalid_request', | 
|  | 105 | +          error_description: 'Invalid content-type', | 
|  | 106 | +          statusCode: 400 | 
|  | 107 | +        })) | 
|  | 108 | +      } | 
|  | 109 | + | 
|  | 110 | +      accessToken = req.body.access_token | 
|  | 111 | +    } | 
|  | 112 | + | 
|  | 113 | +    function invokeVerification () { | 
|  | 114 | +      self.client.verify(accessToken, options) | 
|  | 115 | +        .then(function (accessTokenClaims) { | 
|  | 116 | +          req.accessToken = accessToken | 
|  | 117 | +          req.accessTokenClaims = accessTokenClaims | 
|  | 118 | +          return self.client.userInfo({token: accessToken}) | 
|  | 119 | +        }) | 
|  | 120 | +        .then(function (userInfo) { | 
|  | 121 | +          req.userInfo = userInfo | 
|  | 122 | +          req.session.userId = userInfo.profile | 
|  | 123 | +          req.session.identified = true | 
|  | 124 | +          next() | 
|  | 125 | +        }) | 
|  | 126 | +        .catch(function (err) { | 
|  | 127 | +          nexter(err) | 
|  | 128 | +        }) | 
|  | 129 | +    } | 
|  | 130 | + | 
|  | 131 | +    // Missing access token | 
|  | 132 | +    if (!accessToken) { | 
|  | 133 | +      debug('No authentication token!') | 
|  | 134 | +      next() | 
|  | 135 | +      // return nexter(new UnauthorizedError({ | 
|  | 136 | +      //   realm: 'user', | 
|  | 137 | +      //   error: 'invalid_request', | 
|  | 138 | +      //   error_description: 'An access token is required', | 
|  | 139 | +      //   statusCode: 400 | 
|  | 140 | +      // })) | 
|  | 141 | + | 
|  | 142 | +      // Access token found | 
|  | 143 | +    } else { | 
|  | 144 | +      // If JWKs are not set, attempt to retrieve them first | 
|  | 145 | +      if (!self.client.jwks) { | 
|  | 146 | +        self.client.discover().then(function () { | 
|  | 147 | +            return self.client.getJWKs() | 
|  | 148 | +          }) | 
|  | 149 | +          // then verify the token and carry on | 
|  | 150 | +          .then(invokeVerification) | 
|  | 151 | +          .catch(function (err) { | 
|  | 152 | +            nexter(err) | 
|  | 153 | +          }) | 
|  | 154 | +        // otherwise, verify the token right away | 
|  | 155 | +      } else { | 
|  | 156 | +        invokeVerification() | 
|  | 157 | +      } | 
|  | 158 | +    } | 
|  | 159 | +  } | 
|  | 160 | +} | 
|  | 161 | +AnvilConnectExpress.prototype.verifier = verifier | 
|  | 162 | + | 
|  | 163 | +var url = require('url') | 
|  | 164 | +function fullUrl(req) { | 
|  | 165 | +  return url.format({ | 
|  | 166 | +    protocol: req.protocol, | 
|  | 167 | +    host: req.get('host'), | 
|  | 168 | +    pathname: req.originalUrl | 
|  | 169 | +  }) | 
|  | 170 | +} | 
|  | 171 | + | 
|  | 172 | +function urlForLogin (req) { | 
|  | 173 | +  // return 'https://anvil.local/authorize?stuff' | 
|  | 174 | +  var loginUrl = this.client.authorizationUri({ | 
|  | 175 | +    endpoint: 'signin', | 
|  | 176 | +    nonce: '123', | 
|  | 177 | +    response_mode: 'query', | 
|  | 178 | +    response_type: 'token id_token', | 
|  | 179 | +    redirect_uri: 'https://ldnode.local:8443/rp' | 
|  | 180 | +  }) | 
|  | 181 | +  return loginUrl | 
|  | 182 | +} | 
|  | 183 | +AnvilConnectExpress.prototype.urlForLogin = urlForLogin | 
|  | 184 | + | 
|  | 185 | + | 
|  | 186 | +/** | 
|  | 187 | + * Export | 
|  | 188 | + */ | 
|  | 189 | + | 
|  | 190 | +module.exports = AnvilConnectExpress | 
0 commit comments