mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Revert "Revert "add oauth2-server""
This reverts commit 946a7c2494d39fd7581cb8a068af7df647fb3bda. GitOrigin-RevId: 2f02e9d9e2d0348e4ea1d447e0291fae72c0008a
This commit is contained in:
parent
9b24ed6daa
commit
7883554d73
7 changed files with 280 additions and 74 deletions
|
@ -187,25 +187,41 @@ module.exports = AuthenticationController =
|
|||
return doRequest
|
||||
|
||||
requireOauth: () ->
|
||||
# require this here because module may not be included in some versions
|
||||
Oauth2Server = require "../../../../modules/oauth2-server/app/js/Oauth2Server"
|
||||
return (req, res, next = (error) ->) ->
|
||||
return res.status(401).send() unless req.token?
|
||||
options =
|
||||
expectedStatusCodes: [401]
|
||||
json: token: req.token
|
||||
method: "POST"
|
||||
uri: "/api/v1/sharelatex/oauth_authorize"
|
||||
V1Api.request options, (error, response, body) ->
|
||||
request = new Oauth2Server.Request(req)
|
||||
response = new Oauth2Server.Response(res)
|
||||
Oauth2Server.server.authenticate request, response, {}, (err, token) ->
|
||||
if err?
|
||||
# fall back to v1 on invalid token
|
||||
return AuthenticationController._requireOauthV1Fallback req, res, next if err.code == 401
|
||||
# bubble up all other errors
|
||||
return next(err)
|
||||
req.oauth =
|
||||
access_token: token.accessToken
|
||||
req.oauth_token = token
|
||||
req.oauth_user = token.user
|
||||
return next()
|
||||
|
||||
_requireOauthV1Fallback: (req, res, next) ->
|
||||
return res.sendStatus 401 unless req.token?
|
||||
options =
|
||||
expectedStatusCodes: [401]
|
||||
json: token: req.token
|
||||
method: "POST"
|
||||
uri: "/api/v1/sharelatex/oauth_authorize"
|
||||
V1Api.request options, (error, response, body) ->
|
||||
return next(error) if error?
|
||||
return res.status(401).json({error: "invalid_token"}) unless body?.user_profile?.id
|
||||
User.findOne { "overleaf.id": body.user_profile.id }, (error, user) ->
|
||||
return next(error) if error?
|
||||
return res.status(401).json({error: "invalid_token"}) unless body?.user_profile?.id
|
||||
User.findOne { "overleaf.id": body.user_profile.id }, (error, user) ->
|
||||
return next(error) if error?
|
||||
return res.status(401).send({error: "invalid_token"}) unless user?
|
||||
req.oauth =
|
||||
access_token: body.access_token
|
||||
collabratec_customer_id: body.collabratec_customer_id
|
||||
user_profile: body.user_profile
|
||||
req.oauth_user = user
|
||||
next()
|
||||
return res.status(401).json({error: "invalid_token"}) unless user?
|
||||
req.oauth =
|
||||
access_token: body.access_token
|
||||
user.collabratec_id = body.collabratec_customer_id unless user.collabratec_id?
|
||||
req.oauth_user = user
|
||||
next()
|
||||
|
||||
_globalLoginWhitelist: []
|
||||
addEndpointToLoginWhitelist: (endpoint) ->
|
||||
|
|
31
services/web/app/coffee/models/OauthAccessToken.coffee
Normal file
31
services/web/app/coffee/models/OauthAccessToken.coffee
Normal file
|
@ -0,0 +1,31 @@
|
|||
mongoose = require 'mongoose'
|
||||
Settings = require 'settings-sharelatex'
|
||||
|
||||
Schema = mongoose.Schema
|
||||
ObjectId = Schema.ObjectId
|
||||
|
||||
OauthAccessTokenSchema = new Schema(
|
||||
{
|
||||
accessToken: String
|
||||
accessTokenExpiresAt: Date
|
||||
oauthApplication_id: { type: ObjectId, ref: 'OauthApplication' }
|
||||
refreshToken: String
|
||||
refreshTokenExpiresAt: Date
|
||||
scope: String
|
||||
user_id: { type: ObjectId, ref: 'User' }
|
||||
},
|
||||
{
|
||||
collection: 'oauthAccessTokens'
|
||||
}
|
||||
)
|
||||
|
||||
conn = mongoose.createConnection(Settings.mongo.url, {
|
||||
server: {poolSize: Settings.mongo.poolSize || 10},
|
||||
config: {autoIndex: false}
|
||||
})
|
||||
|
||||
OauthAccessToken = conn.model('OauthAccessToken', OauthAccessTokenSchema)
|
||||
|
||||
mongoose.model 'OauthAccessToken', OauthAccessTokenSchema
|
||||
exports.OauthAccessToken = OauthAccessToken
|
||||
exports.OauthAccessTokenSchema = OauthAccessTokenSchema
|
30
services/web/app/coffee/models/OauthApplication.coffee
Normal file
30
services/web/app/coffee/models/OauthApplication.coffee
Normal file
|
@ -0,0 +1,30 @@
|
|||
mongoose = require 'mongoose'
|
||||
Settings = require 'settings-sharelatex'
|
||||
|
||||
Schema = mongoose.Schema
|
||||
ObjectId = Schema.ObjectId
|
||||
|
||||
OauthApplicationSchema = new Schema(
|
||||
{
|
||||
id: String
|
||||
clientSecret: String
|
||||
grants: [ String ]
|
||||
name: String
|
||||
redirectUris: [ String ]
|
||||
scopes: [ String ]
|
||||
},
|
||||
{
|
||||
collection: 'oauthApplications'
|
||||
}
|
||||
)
|
||||
|
||||
conn = mongoose.createConnection(Settings.mongo.url, {
|
||||
server: {poolSize: Settings.mongo.poolSize || 10},
|
||||
config: {autoIndex: false}
|
||||
})
|
||||
|
||||
OauthApplication = conn.model('OauthApplication', OauthApplicationSchema)
|
||||
|
||||
mongoose.model 'OauthApplication', OauthApplicationSchema
|
||||
exports.OauthApplication = OauthApplication
|
||||
exports.OauthApplicationSchema = OauthApplicationSchema
|
30
services/web/app/coffee/models/OauthAuthorizationCode.coffee
Normal file
30
services/web/app/coffee/models/OauthAuthorizationCode.coffee
Normal file
|
@ -0,0 +1,30 @@
|
|||
mongoose = require 'mongoose'
|
||||
Settings = require 'settings-sharelatex'
|
||||
|
||||
Schema = mongoose.Schema
|
||||
ObjectId = Schema.ObjectId
|
||||
|
||||
OauthAuthorizationCodeSchema = new Schema(
|
||||
{
|
||||
authorizationCode: String
|
||||
expiresAt: Date
|
||||
oauthApplication_id: { type: ObjectId, ref: 'OauthApplication' }
|
||||
redirectUri: String
|
||||
scope: String
|
||||
user_id: { type: ObjectId, ref: 'User' }
|
||||
},
|
||||
{
|
||||
collection: 'oauthAuthorizationCodes'
|
||||
}
|
||||
)
|
||||
|
||||
conn = mongoose.createConnection(Settings.mongo.url, {
|
||||
server: {poolSize: Settings.mongo.poolSize || 10},
|
||||
config: {autoIndex: false}
|
||||
})
|
||||
|
||||
OauthAuthorizationCode = conn.model('OauthAuthorizationCode', OauthAuthorizationCodeSchema)
|
||||
|
||||
mongoose.model 'OauthAuthorizationCode', OauthAuthorizationCodeSchema
|
||||
exports.OauthAuthorizationCode = OauthAuthorizationCode
|
||||
exports.OauthAuthorizationCodeSchema = OauthAuthorizationCodeSchema
|
69
services/web/npm-shrinkwrap.json
generated
69
services/web/npm-shrinkwrap.json
generated
|
@ -2139,6 +2139,18 @@
|
|||
"resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz",
|
||||
"integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs="
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "2.0.1",
|
||||
"from": "basic-auth@>=2.0.0 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
||||
"dependencies": {
|
||||
"safe-buffer": {
|
||||
"version": "5.1.2",
|
||||
"from": "safe-buffer@5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"basic-auth-connect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz",
|
||||
|
@ -3550,6 +3562,16 @@
|
|||
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
|
||||
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
|
||||
},
|
||||
"co-bluebird": {
|
||||
"version": "1.1.0",
|
||||
"from": "co-bluebird@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz"
|
||||
},
|
||||
"co-use": {
|
||||
"version": "1.1.0",
|
||||
"from": "co-use@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz"
|
||||
},
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
|
||||
|
@ -8431,6 +8453,11 @@
|
|||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"is-generator": {
|
||||
"version": "1.0.3",
|
||||
"from": "is-generator@>=1.0.2 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz"
|
||||
},
|
||||
"is-glob": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
|
||||
|
@ -12576,6 +12603,43 @@
|
|||
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
|
||||
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
|
||||
},
|
||||
"oauth2-server": {
|
||||
"version": "3.0.1",
|
||||
"from": "oauth2-server@latest",
|
||||
"resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.1.tgz",
|
||||
"dependencies": {
|
||||
"bluebird": {
|
||||
"version": "3.5.3",
|
||||
"from": "bluebird@>=3.5.1 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz"
|
||||
},
|
||||
"lodash": {
|
||||
"version": "4.17.11",
|
||||
"from": "lodash@>=4.17.10 <5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz"
|
||||
},
|
||||
"mime-db": {
|
||||
"version": "1.38.0",
|
||||
"from": "mime-db@>=1.38.0 <1.39.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz"
|
||||
},
|
||||
"mime-types": {
|
||||
"version": "2.1.22",
|
||||
"from": "mime-types@>=2.1.18 <2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz"
|
||||
},
|
||||
"statuses": {
|
||||
"version": "1.5.0",
|
||||
"from": "statuses@>=1.5.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
|
||||
},
|
||||
"type-is": {
|
||||
"version": "1.6.16",
|
||||
"from": "type-is@>=1.6.16 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -14074,6 +14138,11 @@
|
|||
"is-promise": "~1"
|
||||
}
|
||||
},
|
||||
"promisify-any": {
|
||||
"version": "2.0.1",
|
||||
"from": "promisify-any@>=2.0.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz"
|
||||
},
|
||||
"promisify-call": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz",
|
||||
|
|
|
@ -79,6 +79,7 @@
|
|||
"nodemailer-sendgrid-transport": "^0.2.0",
|
||||
"nodemailer-ses-transport": "^1.3.0",
|
||||
"nvd3": "^1.8.6",
|
||||
"oauth2-server": "^3.0.1",
|
||||
"optimist": "0.6.1",
|
||||
"overleaf-error-type": "git+https://github.com/overleaf/overleaf-error-type.git",
|
||||
"passport": "^0.3.2",
|
||||
|
|
|
@ -37,6 +37,10 @@ describe "AuthenticationController", ->
|
|||
ipMatcherAffiliation: sinon.stub()
|
||||
"../V1/V1Api": @V1Api = request: sinon.stub()
|
||||
"../../models/User": { User: @UserModel }
|
||||
"../../../../modules/oauth2-server/app/js/Oauth2Server": @Oauth2Server =
|
||||
Request: sinon.stub()
|
||||
Response: sinon.stub()
|
||||
server: authenticate: sinon.stub()
|
||||
@user =
|
||||
_id: ObjectId()
|
||||
email: @email = "USER@example.com"
|
||||
|
@ -402,90 +406,115 @@ describe "AuthenticationController", ->
|
|||
|
||||
describe "requireOauth", ->
|
||||
beforeEach ->
|
||||
@res.sendStatus = sinon.stub()
|
||||
@res.send = sinon.stub()
|
||||
@res.status = sinon.stub().returns(@res)
|
||||
@middleware = @AuthenticationController.requireOauth()
|
||||
|
||||
describe "when token not provided", ->
|
||||
describe "when Oauth2Server authenticates", ->
|
||||
beforeEach ->
|
||||
@token =
|
||||
accessToken: "token"
|
||||
user: "user"
|
||||
@Oauth2Server.server.authenticate.yields null, @token
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return 401 error", ->
|
||||
@res.status.should.have.been.calledWith 401
|
||||
it "should set oauth_token on request", ->
|
||||
@req.oauth_token.should.equal @token
|
||||
|
||||
describe "when token provided", ->
|
||||
it "should set oauth on request", ->
|
||||
@req.oauth.access_token.should.equal @token.accessToken
|
||||
|
||||
it "should set oauth_user on request", ->
|
||||
@req.oauth_user.should.equal "user"
|
||||
|
||||
it "should call next", ->
|
||||
@next.should.have.been.calledOnce
|
||||
|
||||
describe "when Oauth2Server does not authenticate", ->
|
||||
beforeEach ->
|
||||
@V1Api.request = sinon.stub().yields("error", {}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
@Oauth2Server.server.authenticate.yields code: 401
|
||||
|
||||
it "should make request to v1 api with token", ->
|
||||
@V1Api.request.should.have.been.calledWith {
|
||||
expectedStatusCodes: [401]
|
||||
json: token: "foo"
|
||||
method: "POST"
|
||||
uri: "/api/v1/sharelatex/oauth_authorize"
|
||||
}
|
||||
|
||||
describe "when v1 api returns error", ->
|
||||
beforeEach ->
|
||||
@V1Api.request = sinon.stub().yields("error", {}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return status", ->
|
||||
@next.should.have.been.calledWith "error"
|
||||
|
||||
describe "when v1 api status code is not 200", ->
|
||||
beforeEach ->
|
||||
@V1Api.request = sinon.stub().yields(null, {statusCode: 401}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return status", ->
|
||||
@res.status.should.have.been.calledWith 401
|
||||
|
||||
describe "when v1 api returns authorized profile and access token", ->
|
||||
beforeEach ->
|
||||
@oauth_authorize =
|
||||
access_token: "access_token"
|
||||
user_profile: id: "overleaf-id"
|
||||
@V1Api.request = sinon.stub().yields(null, {statusCode: 200}, @oauth_authorize)
|
||||
@req.token = "foo"
|
||||
|
||||
describe "in all cases", ->
|
||||
describe "when token not provided", ->
|
||||
beforeEach ->
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should find user", ->
|
||||
@UserModel.findOne.should.have.been.calledWithMatch { "overleaf.id": "overleaf-id" }
|
||||
it "should return 401 error", ->
|
||||
@res.sendStatus.should.have.been.calledWith 401
|
||||
|
||||
describe "when user find returns error", ->
|
||||
describe "when token provided", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields("error")
|
||||
@V1Api.request = sinon.stub().yields("error", {}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return error", ->
|
||||
it "should make request to v1 api with token", ->
|
||||
@V1Api.request.should.have.been.calledWith {
|
||||
expectedStatusCodes: [401]
|
||||
json: token: "foo"
|
||||
method: "POST"
|
||||
uri: "/api/v1/sharelatex/oauth_authorize"
|
||||
}
|
||||
|
||||
describe "when v1 api returns error", ->
|
||||
beforeEach ->
|
||||
@V1Api.request = sinon.stub().yields("error", {}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return status", ->
|
||||
@next.should.have.been.calledWith "error"
|
||||
|
||||
describe "when user is not found", ->
|
||||
describe "when v1 api status code is not 200", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields(null, null)
|
||||
@V1Api.request = sinon.stub().yields(null, {statusCode: 401}, {})
|
||||
@req.token = "foo"
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return unauthorized", ->
|
||||
it "should return status", ->
|
||||
@res.status.should.have.been.calledWith 401
|
||||
|
||||
describe "when user is found", ->
|
||||
describe "when v1 api returns authorized profile and access token", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields(null, "user")
|
||||
@middleware(@req, @res, @next)
|
||||
@oauth_authorize =
|
||||
access_token: "access_token"
|
||||
user_profile: id: "overleaf-id"
|
||||
@V1Api.request = sinon.stub().yields(null, {statusCode: 200}, @oauth_authorize)
|
||||
@req.token = "foo"
|
||||
|
||||
it "should add user to request", ->
|
||||
@req.oauth_user.should.equal "user"
|
||||
describe "in all cases", ->
|
||||
beforeEach ->
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should add access_token to request", ->
|
||||
@req.oauth.access_token.should.equal "access_token"
|
||||
it "should find user", ->
|
||||
@UserModel.findOne.should.have.been.calledWithMatch { "overleaf.id": "overleaf-id" }
|
||||
|
||||
describe "when user find returns error", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields("error")
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return error", ->
|
||||
@next.should.have.been.calledWith "error"
|
||||
|
||||
describe "when user is not found", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields(null, null)
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should return unauthorized", ->
|
||||
@res.status.should.have.been.calledWith 401
|
||||
|
||||
describe "when user is found", ->
|
||||
beforeEach ->
|
||||
@UserModel.findOne = sinon.stub().yields(null, "user")
|
||||
@middleware(@req, @res, @next)
|
||||
|
||||
it "should add user to request", ->
|
||||
@req.oauth_user.should.equal "user"
|
||||
|
||||
it "should add access_token to request", ->
|
||||
@req.oauth.access_token.should.equal "access_token"
|
||||
|
||||
describe "requireGlobalLogin", ->
|
||||
beforeEach ->
|
||||
|
|
Loading…
Reference in a new issue