Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OpenID #149

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@magisterjs/authcode": "*",
"dedent": "^0.7.0",
"lodash": "^4.17.11",
"magister-openid": "^0.1.2",
"moment": "^2.24.0",
"node-fetch": "^2.6.0"
},
Expand Down
20 changes: 11 additions & 9 deletions src/http.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ class Http {
* after the start of a request when it should be timed out.
*/
constructor(requestTimeout = DEFAULT_REQUEST_TIMEOUT) {
/**
* @type {AuthManager}
*/
this.authManager = undefined
/**
* @type {{ queue: Object, timeoutId: ?Number }}
* @private
Expand All @@ -22,11 +26,6 @@ class Http {
queue: [],
timeoutId: undefined,
}
/**
* @type {String}
* @private
*/
this._token = ''
/**
* @type {Number}
* @private
Expand Down Expand Up @@ -72,13 +71,15 @@ class Http {
* @param {Object} obj
* @returns {Request}
*/
makeRequest(obj) {
async makeRequest(obj) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this (the function now being async) create any issues?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, I fixed all the refrences

await this.authManager.checkExpiration()
const accessToken = this.authManager.accessToken
const init = {
method: obj.method,
timeout: this._requestTimeout,
headers: {
...obj.headers,
Authorization: 'Bearer ' + this._token,
Authorization: `Bearer ${accessToken}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Much cleaner.

'X-API-Client-ID': '12D8',
},
redirect: obj.redirect,
Expand All @@ -97,7 +98,7 @@ class Http {
* @param {Object} obj
*/
async _request(obj) {
const request = this.makeRequest(obj)
const request = await this.makeRequest(obj)
const info = this._ratelimit

let res
Expand All @@ -112,7 +113,8 @@ class Http {
}

try {
const parsed = await res.json()
const clone = await res.clone()
const parsed = await clone.json()
if ('SecondsLeft' in parsed) {
// Handle rate limit errors
this._setRatelimitTime(Number.parseInt(parsed.SecondsLeft, 10))
Expand Down
117 changes: 28 additions & 89 deletions src/magister.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import _ from 'lodash'
import fetch from 'node-fetch'
import url from 'url'
import AuthCode from '@magisterjs/authcode'
import { AuthManager } from 'magister-openid'

// internal: used in this file
import AbsenceInfo from './absenceInfo'
Expand Down Expand Up @@ -53,9 +54,9 @@ class Magister {
if (!/^[^.#/\\]+\.magister\.net$/.test(info.host)) {
throw new Error('`school.url` is not a correct magister url')
}
school.tenant = info.host
school.url = `https://${info.host}`


/**
* @type {Object}
* @readonly
Expand All @@ -80,11 +81,18 @@ class Magister {
}

/**
* @type {string}
* @readonly
* @type {AuthManager}
*/
get authManager() {
return this.http.authManager
}

/**
* @type {AuthManager}
* @param {AuthManager} authManager
*/
get token() {
return this.http._token
set authManager(authManager) {
this.http.authManager = authManager
}

/**
Expand Down Expand Up @@ -391,28 +399,23 @@ class Magister {

/**
* Logins to Magister.
* @param {boolean} [forceLogin=false] Force a login, even when a token
* @param {boolean} [forceLogin=false] Force a login, even when a tokenSet
* is in the options object.
* @returns {Promise<string>} A promise that resolves when done logging in. With the current session ID as parameter.
* @returns {Promise<string>} A promise that resolves when done logging in.
*/
async login(forceLogin = false) {
// TODO: clean this code up a bit
const options = this._options
const schoolUrl = this.school.url
const authUrl = await util.getAuthenticationUrl(schoolUrl)

const setToken = token => {
this.http._token = token
return token
}
const school = this.school
const tenant = school.tenant
this.authManager = new AuthManager(tenant, options.tokenSet)

const retrieveAccount = async () => {
const accountData =
await this.http.get(`${schoolUrl}/api/account`).then(res => res.json())
await this.http.get(`${school.url}/api/account`).then(res => res.json())
const id = accountData.Persoon.Id

this._personUrl = `${schoolUrl}/api/personen/${id}`
this._pupilUrl = `${schoolUrl}/api/leerlingen/${id}`
this._personUrl = `${school.url}/api/personen/${id}`
this._pupilUrl = `${school.url}/api/leerlingen/${id}`

this._privileges = new Privileges(this, accountData.Groep[0].Privileges)
// REVIEW: do we want to make profileInfo a function?
Expand All @@ -423,77 +426,13 @@ class Magister {
)
}

if (!forceLogin && options.token) {
setToken(options.token)
return await retrieveAccount()
}

// extract returnUrl
const location = await this.http.get(authUrl, {
redirect: 'manual',
}).then(res => res.headers.get('Location'))
const returnUrl = util.extractQueryParameter(location, 'returnUrl')

// extract session and XSRF related stuff
const xsrfResponse = await this.http.get(location, {
redirect: 'manual',
})

const challengeUrl = 'https://accounts.magister.net/challenge'
const sessionId = util.extractQueryParameter(xsrfResponse.headers.get('Location'), 'sessionId')

const xsrfToken =
xsrfResponse.headers.get('set-cookie')
.split('XSRF-TOKEN=')[1]
.split(';')[0]
const authCookies = xsrfResponse.headers.get('set-cookie').toString()
const headers = {
Cookie: authCookies,
'X-XSRF-TOKEN': xsrfToken,
if (options.tokenSet && !forceLogin) {
this.authManager.tokenSet = options.tokenSet
await this.authManager.checkExpiration()
} else {
await this.authManager.login(options.username, options.password)
}

// test username
await this.http.post(`${challengeUrl}/username`, {
authCode: options.authCode,
sessionId: sessionId,
returnUrl: returnUrl,
username: options.username,
}, { headers })
.then(res => {
if (res.error) {
throw new AuthError(res.error)
} else if (res.status === 400) {
throw new AuthError('Invalid username')
}
return res
})

// test password
headers.Cookie = await this.http.post(`${challengeUrl}/password`, {
authCode: options.authCode,
sessionId: sessionId,
returnUrl: returnUrl,
password: options.password,
}, { headers })
.then(res => {
if (res.error) {
throw new AuthError(res.error)
} else if (res.status === 400) {
throw new AuthError('Invalid password')
}
return res
})
.then(res => res.headers.get('set-cookie'))

// extract bearer token
const res = await this.http.get(`https://accounts.magister.net${returnUrl}`, {
redirect: 'manual',
headers,
})
const tokenRegex = /&access_token=([^&]*)/
const loc = res.headers.get('Location')

setToken(tokenRegex.exec(loc)[1])
return await retrieveAccount()
}
}
Expand Down Expand Up @@ -522,9 +461,9 @@ export default function magister(options) {

if (!(
options.school &&
(options.token || (options.username && options.password))
(options.tokenSet || (options.username && options.password))
)) {
return rej('school and username&password or token are required.')
return rej('school and username&password or tokenSet are required.')
}

if (!_.isObject(options.school)) {
Expand Down
1 change: 0 additions & 1 deletion src/util.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ export * from './utils/common'
export * from './utils/date'
export * from './utils/html'
export * from './utils/string'
export * from './utils/auth'
41 changes: 0 additions & 41 deletions src/utils/auth.js

This file was deleted.

8 changes: 0 additions & 8 deletions src/utils/common.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
import { URL } from 'url'

export function cloneClassInstance(object) {
return Object.assign(Object.create(object), object)
}

export function extractQueryParameter(url, parameter) {
const parsedUrl = new URL(url)

return parsedUrl.searchParams.get(parameter)
}
4 changes: 2 additions & 2 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,13 @@ describe('Magister', function() {
...options,
username: 'xxx',
password: 'xxx',
})).to.be.rejectedWith(magisterjs.AuthError)
})).to.be.rejectedWith(Error)
})

it('should be able to reuthenticate with a token', function () {
return magister({
school: options.school,
token: m.token,
tokenSet: m.authManager.tokenSet,
}).then(mag => m = mag)
})

Expand Down