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

feat: allow openid-client to be optional #40

Merged
merged 3 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ Un wiki est disponible, celui-ci rassemble davantage d'informations sur le fonct
* [.postAbsenceFileState(folderId, reasonId, comment, params)](#Skolengo+postAbsenceFileState)
* [.getAbsenceReasons(limit, offset, params)](#Skolengo+getAbsenceReasons)
* [.refreshToken(triggerListener)](#Skolengo+refreshToken)
* [.getTokenClaims()](#Skolengo+getTokenClaims)
* _static_
* [.revokeToken(oidClient, token)](#Skolengo.revokeToken)
* [.getAppCurrentConfig(httpConfig)](#Skolengo.getAppCurrentConfig)
Expand Down Expand Up @@ -368,7 +369,7 @@ const {Skolengo} = require('scolengo-api')
Skolengo.fromConfigObject(config).then(async user => {
const startDate = new Date().toISOString().split('T')[0] // Aujourd'hui
const endDate = new Date(Date.now() + 15 * 24 * 60 * 60 * 1e3).toISOString().split('T')[0] // Aujourd'hui + 15 jours
const homework = await user.getHomeworkAssignments(user.tokenSet.claims().sub, startDate, endDate)
const homework = await user.getHomeworkAssignments(user.getTokenClaims().sub, startDate, endDate)

console.log("Voici les exercices à faire pour les 2 prochaines semaines :", homework)
})
Expand All @@ -393,7 +394,7 @@ const {Skolengo} = require('scolengo-api')

const user = await Skolengo.fromConfigObject(config)

user.getHomeworkAssignment(user.tokenSet.claims().sub, "123456").then(e => {
user.getHomeworkAssignment(user.getTokenClaims().sub, "123456").then(e => {
console.log(`Pour le ${new Date(e.dueDateTime).toLocaleString()} :`)
console.log(`> ${e.title} (${e.subject.label})`)
console.log(e.html)
Expand Down Expand Up @@ -621,6 +622,12 @@ Demande un renouvellement du jeu de jeton (tokenSet)
| --- | --- | --- | --- |
| triggerListener | <code>boolean</code> | <code>true</code> | Si oui, appeler la fonction onTokenRefresh |

<a name="Skolengo+getTokenClaims"></a>

### skolengo.getTokenClaims()
Récupérer les données contenues dans le payload JWT du token ID

**Kind**: instance method of [<code>Skolengo</code>](#Skolengo)
<a name="Skolengo.revokeToken"></a>

### Skolengo.revokeToken(oidClient, token)
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@
},
"dependencies": {
"axios": "^1.3.5",
"jsonapi-fractal": "^2.3.1",
"jsonapi-fractal": "^2.3.1"
},
"optionalDependencies": {
"openid-client": "^5.4.0"
},
"devDependencies": {
Expand Down
67 changes: 40 additions & 27 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import type { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import type { Client } from 'openid-client'
import { Issuer, TokenSet } from 'openid-client'
import type { Client, IdTokenClaims, TokenSetParameters } from 'openid-client'
import type { DocumentObject } from 'jsonapi-fractal'
import { deserialize, serialize } from 'jsonapi-fractal'
import type { Stream } from 'node:stream'
Expand All @@ -26,8 +25,8 @@ const OID_CLIENT_SECRET = Buffer.from('N2NiNGQ5YTgtMjU4MC00MDQxLTlhZTgtZDU4MDM4N

export class Skolengo {
public readonly school: School
public tokenSet: TokenSet
private readonly oidClient: Client
public tokenSet: TokenSetParameters
private readonly oidClient: Client | null
private readonly config: SkolengoConfig

/**
Expand Down Expand Up @@ -95,7 +94,7 @@ export class Skolengo {
* @param {TokenSet} tokenSet Jetons d'authentification OpenID Connect
* @param {SkolengoConfig} config Configuration optionnelle (stockage du jeton renouvellé, client HTTP personnalisé, gestion des erreurs Pronote, ...)
*/
public constructor (oidClient: Client, school: School, tokenSet: TokenSet, config?: Partial<SkolengoConfig>) {
public constructor (oidClient: Client | null, school: School, tokenSet: TokenSetParameters, config?: Partial<SkolengoConfig>) {
this.oidClient = oidClient
this.school = school
this.tokenSet = tokenSet
Expand Down Expand Up @@ -212,6 +211,8 @@ export class Skolengo {
* ```
*/
public static async getOIDClient (school: School, redirectUri = 'skoapp-prod://sign-in-callback'): Promise<Client> {
const { Issuer } = await import('openid-client')

const skolengoIssuer = await Issuer.discover(school.emsOIDCWellKnownUrl)
return new skolengoIssuer.Client({
client_id: OID_CLIENT_ID,
Expand Down Expand Up @@ -266,9 +267,11 @@ export class Skolengo {
* ```
*/
public static async fromConfigObject (config: AuthConfig, skolengoConfig?: Partial<SkolengoConfig>): Promise<Skolengo> {
const oidClient = await Skolengo.getOIDClient(config.school)
const tokenSet = new TokenSet(config.tokenSet)
return new Skolengo(oidClient, config.school, tokenSet, skolengoConfig)
try {
return new Skolengo(await Skolengo.getOIDClient(config.school), config.school, config.tokenSet, skolengoConfig)
} catch (e) {
return new Skolengo(null, config.school, config.tokenSet, skolengoConfig)
}
}

/**
Expand All @@ -280,7 +283,7 @@ export class Skolengo {
*/
public async getUserInfo (userId?: string, params?: object, includes: string[] = ['school', 'students', 'students.school', 'schools', 'prioritySchool']): Promise<User> {
return deserialize((await this.request<DocumentObject>({
url: `/users-info/${userId ?? this.tokenSet.claims().sub}`,
url: `/users-info/${userId ?? this.getTokenClaims().sub}`,
responseType: 'json',
params: {
/*
Expand Down Expand Up @@ -375,7 +378,7 @@ export class Skolengo {
* @param {string[]} includes Ressources JSON:API à inclure
* @async
*/
public async getEvaluationSettings (studentId: string = this.tokenSet.claims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['periods', 'skillsSetting', 'skillsSetting.skillAcquisitionColors']): Promise<EvaluationSettings[]> {
public async getEvaluationSettings (studentId: string = this.getTokenClaims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['periods', 'skillsSetting', 'skillsSetting.skillAcquisitionColors']): Promise<EvaluationSettings[]> {
return deserialize((await this.request<DocumentObject>({
url: '/evaluations-settings',
responseType: 'json',
Expand Down Expand Up @@ -412,7 +415,7 @@ export class Skolengo {
* @param {string[]} includes Ressources JSON:API à inclure
* @async
*/
public async getEvaluation (studentId: string = this.tokenSet.claims().sub, periodId: string, limit = 20, offset = 0, params?: object, includes: string[] = ['subject', 'evaluations', 'evaluations.evaluationResult', 'evaluations.evaluationResult.subSkillsEvaluationResults', 'evaluations.evaluationResult.subSkillsEvaluationResults.subSkill', 'evaluations.subSkills', 'teachers']): Promise<Evaluation[]> {
public async getEvaluation (studentId: string = this.getTokenClaims().sub, periodId: string, limit = 20, offset = 0, params?: object, includes: string[] = ['subject', 'evaluations', 'evaluations.evaluationResult', 'evaluations.evaluationResult.subSkillsEvaluationResults', 'evaluations.evaluationResult.subSkillsEvaluationResults.subSkill', 'evaluations.subSkills', 'teachers']): Promise<Evaluation[]> {
return deserialize((await this.request<DocumentObject>({
url: '/evaluation-services',
responseType: 'json',
Expand Down Expand Up @@ -451,7 +454,7 @@ export class Skolengo {
* @param {string[]} includes Ressources JSON:API à inclure
* @async
*/
public async getEvaluationDetail (studentId: string = this.tokenSet.claims().sub, evaluationId: string, params?: object, includes: string[] = ['evaluationService', 'evaluationService.subject', 'evaluationService.teachers', 'subSubject', 'subSkills', 'evaluationResult', 'evaluationResult.subSkillsEvaluationResults', 'evaluationResult.subSkillsEvaluationResults.subSkill']): Promise<EvaluationDetail> {
public async getEvaluationDetail (studentId: string = this.getTokenClaims().sub, evaluationId: string, params?: object, includes: string[] = ['evaluationService', 'evaluationService.subject', 'evaluationService.teachers', 'subSubject', 'subSkills', 'evaluationResult', 'evaluationResult.subSkillsEvaluationResults', 'evaluationResult.subSkillsEvaluationResults.subSkill']): Promise<EvaluationDetail> {
return deserialize((await this.request<DocumentObject>({
url: `/evaluations/${evaluationId}`,
responseType: 'json',
Expand Down Expand Up @@ -496,7 +499,7 @@ export class Skolengo {
* })
* ```
*/
public async getPeriodicReportsFiles (studentId: string = this.tokenSet.claims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['period']): Promise<Attachment[]> {
public async getPeriodicReportsFiles (studentId: string = this.getTokenClaims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['period']): Promise<Attachment[]> {
return deserialize((await this.request<DocumentObject>({
url: '/periodic-reports-files',
responseType: 'json',
Expand Down Expand Up @@ -543,7 +546,7 @@ export class Skolengo {
* })
* ```
*/
public async getAgenda (studentId: string = this.tokenSet.claims().sub, startDate: string, endDate: string, limit = 20, offset = 0, params?: object, includes: string[] = ['lessons', 'lessons.subject', 'lessons.teachers', 'homeworkAssignments', 'homeworkAssignments.subject']): Promise<AgendaResponse> {
public async getAgenda (studentId: string = this.getTokenClaims().sub, startDate: string, endDate: string, limit = 20, offset = 0, params?: object, includes: string[] = ['lessons', 'lessons.subject', 'lessons.teachers', 'homeworkAssignments', 'homeworkAssignments.subject']): Promise<AgendaResponse> {
return new AgendaResponse(deserialize((await this.request<DocumentObject>({
url: '/agendas',
responseType: 'json',
Expand Down Expand Up @@ -583,7 +586,7 @@ export class Skolengo {
* @param {string[]} includes Ressources JSON:API à inclure
* @async
*/
public async getLesson (studentId: string = this.tokenSet.claims().sub, lessonId: string, params?: object, includes: string[] = ['teachers', 'contents', 'contents.attachments', 'subject', 'toDoForTheLesson', 'toDoForTheLesson.subject', 'toDoAfterTheLesson', 'toDoAfterTheLesson.subject']): Promise<Lesson> {
public async getLesson (studentId: string = this.getTokenClaims().sub, lessonId: string, params?: object, includes: string[] = ['teachers', 'contents', 'contents.attachments', 'subject', 'toDoForTheLesson', 'toDoForTheLesson.subject', 'toDoAfterTheLesson', 'toDoAfterTheLesson.subject']): Promise<Lesson> {
return deserialize((await this.request<DocumentObject>({
url: `/lessons/${lessonId}`,
responseType: 'json',
Expand Down Expand Up @@ -613,14 +616,14 @@ export class Skolengo {
* Skolengo.fromConfigObject(config).then(async user => {
* const startDate = new Date().toISOString().split('T')[0] // Aujourd'hui
* const endDate = new Date(Date.now() + 15 * 24 * 60 * 60 * 1e3).toISOString().split('T')[0] // Aujourd'hui + 15 jours
* const homework = await user.getHomeworkAssignments(user.tokenSet.claims().sub, startDate, endDate)
* const homework = await user.getHomeworkAssignments(user.getTokenClaims().sub, startDate, endDate)
*
* console.log("Voici les exercices à faire pour les 2 prochaines semaines :", homework)
* })
* ```
* @async
*/
public async getHomeworkAssignments (studentId: string = this.tokenSet.claims().sub, startDate: string, endDate: string, limit = 20, offset = 0, params?: object, includes: string[] = ['subject', 'teacher', 'teacher.person']): Promise<HomeworkAssignment[]> {
public async getHomeworkAssignments (studentId: string = this.getTokenClaims().sub, startDate: string, endDate: string, limit = 20, offset = 0, params?: object, includes: string[] = ['subject', 'teacher', 'teacher.person']): Promise<HomeworkAssignment[]> {
return deserialize((await this.request<DocumentObject>({
url: '/homework-assignments',
responseType: 'json',
Expand Down Expand Up @@ -658,7 +661,7 @@ export class Skolengo {
*
* const user = await Skolengo.fromConfigObject(config)
*
* user.getHomeworkAssignment(user.tokenSet.claims().sub, "123456").then(e => {
* user.getHomeworkAssignment(user.getTokenClaims().sub, "123456").then(e => {
* console.log(`Pour le ${new Date(e.dueDateTime).toLocaleString()} :`)
* console.log(`> ${e.title} (${e.subject.label})`)
* console.log(e.html)
Expand All @@ -667,7 +670,7 @@ export class Skolengo {
* ```
* @async
*/
public async getHomeworkAssignment (studentId: string = this.tokenSet.claims().sub, homeworkId: string, params?: object, includes: string[] = ['subject', 'teacher', 'pedagogicContent', 'individualCorrectedWork', 'individualCorrectedWork.attachments', 'individualCorrectedWork.audio', 'commonCorrectedWork', 'commonCorrectedWork.attachments', 'commonCorrectedWork.audio', 'commonCorrectedWork.pedagogicContent', 'attachments', 'audio', 'teacher.person']): Promise<HomeworkAssignment> {
public async getHomeworkAssignment (studentId: string = this.getTokenClaims().sub, homeworkId: string, params?: object, includes: string[] = ['subject', 'teacher', 'pedagogicContent', 'individualCorrectedWork', 'individualCorrectedWork.attachments', 'individualCorrectedWork.audio', 'commonCorrectedWork', 'commonCorrectedWork.attachments', 'commonCorrectedWork.audio', 'commonCorrectedWork.pedagogicContent', 'attachments', 'audio', 'teacher.person']): Promise<HomeworkAssignment> {
return deserialize((await this.request<DocumentObject>({
url: `/homework-assignments/${homeworkId}`,
responseType: 'json',
Expand Down Expand Up @@ -703,7 +706,7 @@ export class Skolengo {
* ```
* @async
*/
public async patchHomeworkAssignment (studentId: string = this.tokenSet.claims().sub, homeworkId: string, attributes: Partial<HomeworkAssignment>, params?: object, includes: string[] = ['subject', 'teacher', 'pedagogicContent', 'individualCorrectedWork', 'individualCorrectedWork.attachments', 'individualCorrectedWork.audio', 'commonCorrectedWork', 'commonCorrectedWork.attachments', 'commonCorrectedWork.audio', 'commonCorrectedWork.pedagogicContent', 'attachments', 'audio', 'teacher.person']): Promise<HomeworkAssignment> {
public async patchHomeworkAssignment (studentId: string = this.getTokenClaims().sub, homeworkId: string, attributes: Partial<HomeworkAssignment>, params?: object, includes: string[] = ['subject', 'teacher', 'pedagogicContent', 'individualCorrectedWork', 'individualCorrectedWork.attachments', 'individualCorrectedWork.audio', 'commonCorrectedWork', 'commonCorrectedWork.attachments', 'commonCorrectedWork.audio', 'commonCorrectedWork.pedagogicContent', 'attachments', 'audio', 'teacher.person']): Promise<HomeworkAssignment> {
return deserialize((await this.request<DocumentObject>({
method: 'patch',
url: `/homework-assignments/${homeworkId}`,
Expand Down Expand Up @@ -735,7 +738,7 @@ export class Skolengo {
*/
public async getUsersMailSettings (userId?: string, params?: object, includes: string[] = ['signature', 'folders', 'folders.parent', 'contacts', 'contacts.person', 'contacts.personContacts']): Promise<UsersMailSettings> {
return deserialize((await this.request<DocumentObject>({
url: `/users-mail-settings/${userId ?? this.tokenSet.claims().sub}`,
url: `/users-mail-settings/${userId ?? this.getTokenClaims().sub}`,
params: {
include: includes.join(','),
...params
Expand Down Expand Up @@ -854,7 +857,7 @@ export class Skolengo {
responseType: 'json',
params: {
filter: {
'user.id': userId ?? this.tokenSet.claims().sub
'user.id': userId ?? this.getTokenClaims().sub
},
...params
},
Expand Down Expand Up @@ -922,7 +925,7 @@ export class Skolengo {
* })
* ```
*/
public async getAbsenceFiles (studentId: string = this.tokenSet.claims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['currentState', 'currentState.absenceReason', 'currentState.absenceRecurrence']): Promise<AbsenceFilesResponse> {
public async getAbsenceFiles (studentId: string = this.getTokenClaims().sub, limit = 20, offset = 0, params?: object, includes: string[] = ['currentState', 'currentState.absenceReason', 'currentState.absenceRecurrence']): Promise<AbsenceFilesResponse> {
return new AbsenceFilesResponse(deserialize((await this.request<DocumentObject>({
url: '/absence-files',
responseType: 'json',
Expand Down Expand Up @@ -1024,15 +1027,27 @@ export class Skolengo {
* Demande un renouvellement du jeu de jeton (tokenSet)
* @param {boolean} triggerListener Si oui, appeler la fonction onTokenRefresh
*/
public async refreshToken (triggerListener: boolean = true): Promise<TokenSet> {
const newTokenSet = await this.oidClient.refresh(this.tokenSet)
public async refreshToken (triggerListener: boolean = true): Promise<TokenSetParameters> {
const { TokenSet } = await import('openid-client')

if (this.oidClient === null) throw new Error('Impossible de rafraîchir le jeton sans la librairie openid-client.')

const newTokenSet = await this.oidClient.refresh(new TokenSet(this.tokenSet))

if (triggerListener) this.config.onTokenRefresh(newTokenSet)

this.tokenSet = newTokenSet
return newTokenSet
}

/**
* Récupérer les données contenues dans le payload JWT du token ID
*/
public getTokenClaims (): IdTokenClaims {
if (this.tokenSet.id_token === undefined) throw new TypeError('id_token not present in TokenSet')
return JSON.parse(atob((this.tokenSet.id_token).split('.')[1]))
}

/**
* Gérer l'erreur *PRONOTE_RESOURCES_NOT_READY* obtenue lorsque Skolengo tente d'obtenir les dernières notes d'un élève.
* Ce comportement peut être activé en modifiant le paramètre optionnel correspondant.
Expand Down Expand Up @@ -1097,5 +1112,3 @@ export class Skolengo {
}
}
}

export { TokenSet }
Loading