@openstax/ts-utils 1.3.2 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/config/envConfig.d.ts +1 -1
- package/dist/cjs/services/accountsGateway/index.d.ts +77 -0
- package/dist/cjs/services/accountsGateway/index.js +110 -0
- package/dist/cjs/services/apiGateway/index.d.ts +3 -0
- package/dist/cjs/services/authProvider/browser.d.ts +2 -2
- package/dist/cjs/services/authProvider/browser.js +2 -2
- package/dist/cjs/services/authProvider/subrequest.d.ts +2 -2
- package/dist/cjs/services/authProvider/subrequest.js +2 -2
- package/dist/cjs/services/launchParams/index.d.ts +2 -0
- package/dist/cjs/services/launchParams/index.js +7 -0
- package/dist/cjs/services/launchParams/signer.d.ts +27 -0
- package/dist/cjs/services/launchParams/signer.js +45 -0
- package/dist/cjs/services/launchParams/verifier.d.ts +21 -0
- package/dist/cjs/services/launchParams/verifier.js +58 -0
- package/dist/cjs/services/lrsGateway/xapiUtils.d.ts +32 -0
- package/dist/cjs/services/lrsGateway/xapiUtils.js +61 -0
- package/dist/cjs/services/searchProvider/memorySearchTheBadWay.js +4 -1
- package/dist/cjs/tsconfig.without-specs.cjs.tsbuildinfo +1 -1
- package/dist/esm/config/envConfig.d.ts +1 -1
- package/dist/esm/services/accountsGateway/index.d.ts +77 -0
- package/dist/esm/services/accountsGateway/index.js +103 -0
- package/dist/esm/services/apiGateway/index.d.ts +3 -0
- package/dist/esm/services/authProvider/browser.d.ts +2 -2
- package/dist/esm/services/authProvider/browser.js +2 -2
- package/dist/esm/services/authProvider/subrequest.d.ts +2 -2
- package/dist/esm/services/authProvider/subrequest.js +2 -2
- package/dist/esm/services/launchParams/index.d.ts +2 -0
- package/dist/esm/services/launchParams/index.js +2 -0
- package/dist/esm/services/launchParams/signer.d.ts +27 -0
- package/dist/esm/services/launchParams/signer.js +38 -0
- package/dist/esm/services/launchParams/verifier.d.ts +21 -0
- package/dist/esm/services/launchParams/verifier.js +51 -0
- package/dist/esm/services/lrsGateway/xapiUtils.d.ts +32 -0
- package/dist/esm/services/lrsGateway/xapiUtils.js +55 -0
- package/dist/esm/services/searchProvider/memorySearchTheBadWay.js +4 -1
- package/dist/esm/tsconfig.without-specs.esm.tsbuildinfo +1 -1
- package/package.json +5 -1
|
@@ -21,4 +21,4 @@ export declare const ENV_BUILD_CONFIGS: string[];
|
|
|
21
21
|
*
|
|
22
22
|
* @example const config = { configValue: envConfig('environment_variable_name') };
|
|
23
23
|
*/
|
|
24
|
-
export declare const envConfig: (name: string, type?: 'build' | 'runtime', defaultValue?: string | undefined) => ConfigValueProvider<string>;
|
|
24
|
+
export declare const envConfig: (name: string, type?: 'build' | 'runtime', defaultValue?: ConfigValueProvider<string> | undefined) => ConfigValueProvider<string>;
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
2
|
+
import { GenericFetch } from '../../fetch';
|
|
3
|
+
import { JsonCompatibleStruct } from '../../routing';
|
|
4
|
+
import { ApiUser } from '../authProvider';
|
|
5
|
+
import { Logger } from '../logger';
|
|
6
|
+
export declare type Config = {
|
|
7
|
+
accountsBase: string;
|
|
8
|
+
accountsAuthToken: string;
|
|
9
|
+
};
|
|
10
|
+
interface Initializer<C> {
|
|
11
|
+
configSpace?: C;
|
|
12
|
+
fetch: GenericFetch;
|
|
13
|
+
}
|
|
14
|
+
export declare type FindUserPayload = ({
|
|
15
|
+
external_id: string;
|
|
16
|
+
} | {
|
|
17
|
+
uuid: string;
|
|
18
|
+
}) & {
|
|
19
|
+
sso?: string;
|
|
20
|
+
};
|
|
21
|
+
export declare type FindOrCreateUserPayload = {
|
|
22
|
+
external_id: string;
|
|
23
|
+
email?: string;
|
|
24
|
+
already_verified?: boolean;
|
|
25
|
+
first_name?: string;
|
|
26
|
+
last_name?: string;
|
|
27
|
+
full_name?: string;
|
|
28
|
+
salesforce_contact_id?: string;
|
|
29
|
+
faculty_status?: string;
|
|
30
|
+
role?: string;
|
|
31
|
+
school_type?: string;
|
|
32
|
+
is_test?: boolean;
|
|
33
|
+
sso?: string;
|
|
34
|
+
};
|
|
35
|
+
export declare type FindOrCreateUserResponse = {
|
|
36
|
+
id: number;
|
|
37
|
+
uuid: string;
|
|
38
|
+
external_ids: string[];
|
|
39
|
+
is_test: boolean;
|
|
40
|
+
sso: string;
|
|
41
|
+
};
|
|
42
|
+
export declare type FindUserResponse = (FindOrCreateUserResponse & {
|
|
43
|
+
external_ids: string[];
|
|
44
|
+
}) | undefined;
|
|
45
|
+
export declare type LinkUserPayload = {
|
|
46
|
+
userId: number;
|
|
47
|
+
externalId: string;
|
|
48
|
+
};
|
|
49
|
+
export declare type LinkUserResponse = {
|
|
50
|
+
user_id: number;
|
|
51
|
+
external_id: string;
|
|
52
|
+
};
|
|
53
|
+
export declare type SearchUsersPayload = {
|
|
54
|
+
q: string;
|
|
55
|
+
order_by?: string;
|
|
56
|
+
};
|
|
57
|
+
export declare type SearchUsersResponse = {
|
|
58
|
+
items: Array<ApiUser & {
|
|
59
|
+
external_ids: string[];
|
|
60
|
+
} & JsonCompatibleStruct>;
|
|
61
|
+
total_count: number;
|
|
62
|
+
};
|
|
63
|
+
export declare const accountsGateway: <C extends string = "accounts">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
64
|
+
accountsBase: import("../../config").ConfigValueProvider<string>;
|
|
65
|
+
accountsAuthToken: import("../../config").ConfigValueProvider<string>;
|
|
66
|
+
}; }) => {
|
|
67
|
+
findOrCreateUser: (body: FindOrCreateUserPayload) => Promise<FindOrCreateUserResponse>;
|
|
68
|
+
findUser: (body: FindUserPayload) => Promise<FindUserResponse>;
|
|
69
|
+
getUser: (token: string) => Promise<ApiUser & JsonCompatibleStruct>;
|
|
70
|
+
linkUser: (body: LinkUserPayload) => Promise<LinkUserResponse>;
|
|
71
|
+
mapUserUuids: <T>(userUuidsMap: {
|
|
72
|
+
[uuid: string]: T;
|
|
73
|
+
}, logger: Logger, platformId?: string | undefined) => Promise<[string, T][]>;
|
|
74
|
+
searchUsers: (payload: SearchUsersPayload) => Promise<SearchUsersResponse>;
|
|
75
|
+
};
|
|
76
|
+
export declare type AccountsGateway = ReturnType<ReturnType<typeof accountsGateway>>;
|
|
77
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.accountsGateway = void 0;
|
|
7
|
+
const lodash_1 = require("lodash");
|
|
8
|
+
const query_string_1 = __importDefault(require("query-string"));
|
|
9
|
+
const config_1 = require("../../config");
|
|
10
|
+
const guards_1 = require("../../guards");
|
|
11
|
+
const routing_1 = require("../../routing");
|
|
12
|
+
const logger_1 = require("../logger");
|
|
13
|
+
class ApiError extends Error {
|
|
14
|
+
constructor(message, status) {
|
|
15
|
+
super(message);
|
|
16
|
+
this.status = status;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const accountsGateway = (initializer) => (configProvider) => {
|
|
20
|
+
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'accounts')];
|
|
21
|
+
const accountsBase = (0, config_1.resolveConfigValue)(config.accountsBase);
|
|
22
|
+
const accountsAuthToken = (0, config_1.resolveConfigValue)(config.accountsAuthToken);
|
|
23
|
+
const request = async (method, path, options, statuses = [200, 201]) => {
|
|
24
|
+
const host = (await accountsBase).replace(/\/+$/, '');
|
|
25
|
+
const url = `${host}/api/${path}`;
|
|
26
|
+
const config = {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${options.token || await accountsAuthToken}`,
|
|
29
|
+
},
|
|
30
|
+
method,
|
|
31
|
+
};
|
|
32
|
+
if (options.body) {
|
|
33
|
+
config.body = JSON.stringify(options.body);
|
|
34
|
+
}
|
|
35
|
+
const response = await initializer.fetch(url, config);
|
|
36
|
+
if (!statuses.includes(response.status)) {
|
|
37
|
+
throw new ApiError(`Received unexpected status code ${response.status} for Accounts API call: ${method} ${url}`, response.status);
|
|
38
|
+
}
|
|
39
|
+
return response.json();
|
|
40
|
+
};
|
|
41
|
+
const findOrCreateUser = async (body) => request(routing_1.METHOD.POST, 'user/find-or-create', { body });
|
|
42
|
+
const findUser = async (body) => {
|
|
43
|
+
try {
|
|
44
|
+
return await request(routing_1.METHOD.POST, 'user/find', { body });
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
throw error;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
const getUser = async (token) => request(routing_1.METHOD.GET, 'user', { token });
|
|
56
|
+
const linkUser = async (body) => request(routing_1.METHOD.POST, 'user/external-ids', {
|
|
57
|
+
body: {
|
|
58
|
+
external_id: body.externalId,
|
|
59
|
+
user_id: body.userId,
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
const searchUsers = async (payload) => request(routing_1.METHOD.GET, `users?${query_string_1.default.stringify(payload)}`, {});
|
|
63
|
+
const getPlatformUserId = (externalIds, platformId) => {
|
|
64
|
+
for (const externalId of externalIds) {
|
|
65
|
+
const [userPlatformId, userId] = externalId.split('/', 2);
|
|
66
|
+
if (userPlatformId === platformId) {
|
|
67
|
+
return userId;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
/*
|
|
72
|
+
* If a platformId is given, returns an array where
|
|
73
|
+
* the first element is the user id from the platform
|
|
74
|
+
* and the second is the value from the map
|
|
75
|
+
* Otherwise, returns an array where
|
|
76
|
+
* the first element is the user's full_name
|
|
77
|
+
* and the second is the value from the map
|
|
78
|
+
*/
|
|
79
|
+
const mapUserUuids = async (userUuidsMap, logger, platformId) => {
|
|
80
|
+
const results = [];
|
|
81
|
+
// Accounts will not return any results if this search returns more than 10 users
|
|
82
|
+
const chunkedUuids = (0, lodash_1.chunk)(Object.keys(userUuidsMap), 10);
|
|
83
|
+
await Promise.all(chunkedUuids.map(async (uuids) => {
|
|
84
|
+
const { items } = await searchUsers({ q: uuids.map((uuid) => `uuid:${uuid}`).join(' ') });
|
|
85
|
+
const accountsUuids = items.map((user) => user.uuid);
|
|
86
|
+
if (!(0, lodash_1.isEqual)(accountsUuids.sort(), uuids.sort())) {
|
|
87
|
+
logger.logEvent(logger_1.Level.Warn, {
|
|
88
|
+
message: 'Unexpected Accounts user search results',
|
|
89
|
+
uuids,
|
|
90
|
+
accountsUuids,
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
items.forEach((user) => {
|
|
94
|
+
const userId = platformId ? getPlatformUserId(user.external_ids, platformId) : user.full_name;
|
|
95
|
+
if (!userId) {
|
|
96
|
+
const missing = platformId ? 'external_id matching the given platformId' : 'full_name';
|
|
97
|
+
logger.logEvent(logger_1.Level.Warn, {
|
|
98
|
+
message: `Accounts user has no ${missing}`,
|
|
99
|
+
accountsUuid: user.uuid,
|
|
100
|
+
platformId,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
results.push([userId || 'N/A', userUuidsMap[user.uuid]]);
|
|
104
|
+
});
|
|
105
|
+
}));
|
|
106
|
+
return results;
|
|
107
|
+
};
|
|
108
|
+
return { findOrCreateUser, findUser, getUser, linkUser, mapUserUuids, searchUsers };
|
|
109
|
+
};
|
|
110
|
+
exports.accountsGateway = accountsGateway;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Headers } from 'node-fetch';
|
|
1
2
|
import { ConfigProviderForConfig } from '../../config';
|
|
2
3
|
import { ConfigForFetch, GenericFetch, Response } from '../../fetch';
|
|
3
4
|
import { AnyRoute, ApiResponse, OutputForRoute, ParamsForRoute, PayloadForRoute, QueryParams } from '../../routing';
|
|
@@ -24,11 +25,13 @@ interface AcceptStatus<Ro> {
|
|
|
24
25
|
<S extends number[]>(...args: S): ApiClientResponse<any>;
|
|
25
26
|
}
|
|
26
27
|
declare type UnsafeApiClientResponse<Ro> = {
|
|
28
|
+
headers: Headers;
|
|
27
29
|
load: () => Promise<any>;
|
|
28
30
|
status: number;
|
|
29
31
|
acceptStatus: AcceptStatus<Ro>;
|
|
30
32
|
};
|
|
31
33
|
declare type ApiClientResponse<Ro> = Ro extends any ? {
|
|
34
|
+
headers: Headers;
|
|
32
35
|
status: TResponseStatus<Ro>;
|
|
33
36
|
load: () => Promise<TResponsePayload<Ro>>;
|
|
34
37
|
} : never;
|
|
@@ -2,7 +2,7 @@ import { ConfigProviderForConfig } from '../../config';
|
|
|
2
2
|
import { FetchConfig, GenericFetch } from '../../fetch';
|
|
3
3
|
import { User } from '.';
|
|
4
4
|
declare type Config = {
|
|
5
|
-
|
|
5
|
+
accountsBase: string;
|
|
6
6
|
};
|
|
7
7
|
interface Initializer<C> {
|
|
8
8
|
configSpace?: C;
|
|
@@ -28,7 +28,7 @@ export interface Window {
|
|
|
28
28
|
removeEventListener: (event: 'message', callback: EventHandler) => void;
|
|
29
29
|
}
|
|
30
30
|
export declare const browserAuthProvider: <C extends string = "auth">({ window, configSpace }: Initializer<C>) => (configProvider: { [key in C]: {
|
|
31
|
-
|
|
31
|
+
accountsBase: import("../../config").ConfigValueProvider<string>;
|
|
32
32
|
}; }) => {
|
|
33
33
|
/**
|
|
34
34
|
* adds auth parameters to the url. this is only safe to use when using javascript to navigate
|
|
@@ -7,7 +7,7 @@ const guards_1 = require("../../guards");
|
|
|
7
7
|
const embeddedAuthProvider_1 = require("./utils/embeddedAuthProvider");
|
|
8
8
|
const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
|
|
9
9
|
const config = configProvider[(0, guards_1.ifDefined)(configSpace, 'auth')];
|
|
10
|
-
const
|
|
10
|
+
const accountsBase = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsBase));
|
|
11
11
|
const queryString = window.location.search;
|
|
12
12
|
const queryKey = 'auth';
|
|
13
13
|
const authQuery = new URLSearchParams(queryString).get(queryKey);
|
|
@@ -71,7 +71,7 @@ const browserAuthProvider = ({ window, configSpace }) => (configProvider) => {
|
|
|
71
71
|
* requests user identity from accounts api using given token or cookie
|
|
72
72
|
*/
|
|
73
73
|
const getFetchUser = async () => {
|
|
74
|
-
const response = await window.fetch((await
|
|
74
|
+
const response = await window.fetch((await accountsBase()).replace(/\/+$/, '') + '/api/user', getAuthorizedFetchConfigFromData(userData));
|
|
75
75
|
if (response.status === 200) {
|
|
76
76
|
return { ...userData, user: await response.json() };
|
|
77
77
|
}
|
|
@@ -3,7 +3,7 @@ import { GenericFetch } from '../../fetch';
|
|
|
3
3
|
import { CookieAuthProvider } from '.';
|
|
4
4
|
declare type Config = {
|
|
5
5
|
cookieName: string;
|
|
6
|
-
|
|
6
|
+
accountsBase: string;
|
|
7
7
|
};
|
|
8
8
|
interface Initializer<C> {
|
|
9
9
|
configSpace?: C;
|
|
@@ -11,6 +11,6 @@ interface Initializer<C> {
|
|
|
11
11
|
}
|
|
12
12
|
export declare const subrequestAuthProvider: <C extends string = "subrequest">(initializer: Initializer<C>) => (configProvider: { [key in C]: {
|
|
13
13
|
cookieName: import("../../config").ConfigValueProvider<string>;
|
|
14
|
-
|
|
14
|
+
accountsBase: import("../../config").ConfigValueProvider<string>;
|
|
15
15
|
}; }) => CookieAuthProvider;
|
|
16
16
|
export {};
|
|
@@ -8,7 +8,7 @@ const _1 = require(".");
|
|
|
8
8
|
const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
9
9
|
const config = configProvider[(0, guards_1.ifDefined)(initializer.configSpace, 'subrequest')];
|
|
10
10
|
const cookieName = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.cookieName));
|
|
11
|
-
const
|
|
11
|
+
const accountsBase = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.accountsBase));
|
|
12
12
|
return ({ request, profile }) => {
|
|
13
13
|
let user;
|
|
14
14
|
const getAuthorizedFetchConfig = profile.track('getAuthorizedFetchConfig', () => async () => {
|
|
@@ -23,7 +23,7 @@ const subrequestAuthProvider = (initializer) => (configProvider) => {
|
|
|
23
23
|
if (!token) {
|
|
24
24
|
return undefined;
|
|
25
25
|
}
|
|
26
|
-
return p.trackFetch(initializer.fetch)(await
|
|
26
|
+
return p.trackFetch(initializer.fetch)((await accountsBase()).replace(/\/+$/, '') + '/api/user', { headers })
|
|
27
27
|
.then(response => response.json());
|
|
28
28
|
});
|
|
29
29
|
return {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createLaunchVerifier = exports.createLaunchSigner = void 0;
|
|
4
|
+
var signer_1 = require("./signer");
|
|
5
|
+
Object.defineProperty(exports, "createLaunchSigner", { enumerable: true, get: function () { return signer_1.createLaunchSigner; } });
|
|
6
|
+
var verifier_1 = require("./verifier");
|
|
7
|
+
Object.defineProperty(exports, "createLaunchVerifier", { enumerable: true, get: function () { return verifier_1.createLaunchVerifier; } });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { JWK } from 'node-jose';
|
|
2
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
3
|
+
declare type Config = {
|
|
4
|
+
alg: string;
|
|
5
|
+
expiresIn: string;
|
|
6
|
+
iss: string;
|
|
7
|
+
privateKey: string;
|
|
8
|
+
};
|
|
9
|
+
interface Initializer<C> {
|
|
10
|
+
configSpace?: C;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a class that can sign launch params
|
|
14
|
+
*/
|
|
15
|
+
export declare const createLaunchSigner: <C extends string = "launch">({ configSpace }: Initializer<C>) => (configProvider: { [key in C]: {
|
|
16
|
+
alg: import("../../config").ConfigValueProvider<string>;
|
|
17
|
+
expiresIn: import("../../config").ConfigValueProvider<string>;
|
|
18
|
+
iss: import("../../config").ConfigValueProvider<string>;
|
|
19
|
+
privateKey: import("../../config").ConfigValueProvider<string>;
|
|
20
|
+
}; }) => {
|
|
21
|
+
jwks: () => Promise<{
|
|
22
|
+
keys: JWK.RawKey[];
|
|
23
|
+
}>;
|
|
24
|
+
sign: (subject: string) => Promise<string>;
|
|
25
|
+
};
|
|
26
|
+
export declare type LaunchSigner = ReturnType<ReturnType<typeof createLaunchSigner>>;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLaunchSigner = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const node_jose_1 = require("node-jose");
|
|
9
|
+
const __1 = require("../..");
|
|
10
|
+
const config_1 = require("../../config");
|
|
11
|
+
const guards_1 = require("../../guards");
|
|
12
|
+
const SUPPORTED_ALGORITHMS = [
|
|
13
|
+
'RS256', 'RS384', 'RS512', 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'PS256', 'PS384', 'PS512'
|
|
14
|
+
];
|
|
15
|
+
const assertAlg = (alg) => {
|
|
16
|
+
if ((SUPPORTED_ALGORITHMS).includes(alg)) {
|
|
17
|
+
return alg;
|
|
18
|
+
}
|
|
19
|
+
throw new Error(`"${alg}" is not a valid algorithm`);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Creates a class that can sign launch params
|
|
23
|
+
*/
|
|
24
|
+
const createLaunchSigner = ({ configSpace }) => (configProvider) => {
|
|
25
|
+
const config = configProvider[(0, guards_1.ifDefined)(configSpace, 'launch')];
|
|
26
|
+
const getAlg = (0, __1.once)(async () => assertAlg(await (0, config_1.resolveConfigValue)(config.alg)));
|
|
27
|
+
const getExpiresIn = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.expiresIn));
|
|
28
|
+
const getIss = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.iss));
|
|
29
|
+
const getPrivateKey = (0, __1.once)(() => (0, config_1.resolveConfigValue)(config.privateKey));
|
|
30
|
+
const getKeyStore = (0, __1.once)(async () => {
|
|
31
|
+
const keystore = node_jose_1.JWK.createKeyStore();
|
|
32
|
+
await keystore.add(await getPrivateKey(), 'pem');
|
|
33
|
+
return keystore;
|
|
34
|
+
});
|
|
35
|
+
const jwks = async () => (await getKeyStore()).toJSON(false);
|
|
36
|
+
const sign = async (subject) => {
|
|
37
|
+
const alg = await getAlg();
|
|
38
|
+
const expiresIn = await getExpiresIn();
|
|
39
|
+
const iss = await getIss();
|
|
40
|
+
const header = { alg, iss };
|
|
41
|
+
return jsonwebtoken_1.default.sign({}, await getPrivateKey(), { algorithm: alg, expiresIn, header, issuer: iss, subject });
|
|
42
|
+
};
|
|
43
|
+
return { jwks, sign };
|
|
44
|
+
};
|
|
45
|
+
exports.createLaunchSigner = createLaunchSigner;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { JWK } from 'node-jose';
|
|
2
|
+
import { ConfigProviderForConfig } from '../../config';
|
|
3
|
+
declare type Config = {
|
|
4
|
+
trustedDomain: string;
|
|
5
|
+
};
|
|
6
|
+
interface Initializer<C> {
|
|
7
|
+
configSpace?: C;
|
|
8
|
+
fetcher?: (uri: string) => Promise<{
|
|
9
|
+
keys: JWK.RawKey[];
|
|
10
|
+
}>;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Creates a class that can verify launch params
|
|
14
|
+
*/
|
|
15
|
+
export declare const createLaunchVerifier: <C extends string = "launch">({ configSpace, fetcher }: Initializer<C>) => (configProvider: { [key in C]: {
|
|
16
|
+
trustedDomain: import("../../config").ConfigValueProvider<string>;
|
|
17
|
+
}; }) => {
|
|
18
|
+
verify: (token: string) => Promise<string>;
|
|
19
|
+
};
|
|
20
|
+
export declare type LaunchVerifier = ReturnType<ReturnType<typeof createLaunchVerifier>>;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.createLaunchVerifier = void 0;
|
|
7
|
+
const jsonwebtoken_1 = __importDefault(require("jsonwebtoken"));
|
|
8
|
+
const jwks_rsa_1 = require("jwks-rsa");
|
|
9
|
+
const __1 = require("../..");
|
|
10
|
+
const config_1 = require("../../config");
|
|
11
|
+
const guards_1 = require("../../guards");
|
|
12
|
+
/**
|
|
13
|
+
* Creates a class that can verify launch params
|
|
14
|
+
*/
|
|
15
|
+
const createLaunchVerifier = ({ configSpace, fetcher }) => (configProvider) => {
|
|
16
|
+
const config = configProvider[(0, guards_1.ifDefined)(configSpace, 'launch')];
|
|
17
|
+
const getJwksClient = (0, __1.memoize)((jwksUri) => new jwks_rsa_1.JwksClient({ fetcher, jwksUri }));
|
|
18
|
+
const getJwksKey = (0, __1.memoize)(async (jwksUri, kid) => {
|
|
19
|
+
const client = getJwksClient(jwksUri);
|
|
20
|
+
const key = await client.getSigningKey(kid);
|
|
21
|
+
return key.getPublicKey();
|
|
22
|
+
});
|
|
23
|
+
const getKey = async (header, callback) => {
|
|
24
|
+
// The JWT spec allows iss in the header as a copy of the iss claim, but we require it
|
|
25
|
+
if (!header.iss) {
|
|
26
|
+
return callback(new Error('JWT header missing iss claim'));
|
|
27
|
+
}
|
|
28
|
+
const { iss, kid } = header;
|
|
29
|
+
try {
|
|
30
|
+
const jwksUrl = new URL('/.well-known/jwks.json', iss);
|
|
31
|
+
const launchDomain = jwksUrl.hostname;
|
|
32
|
+
const trustedDomain = await (0, config_1.resolveConfigValue)(config.trustedDomain);
|
|
33
|
+
if (launchDomain !== trustedDomain && !launchDomain.endsWith(`.${trustedDomain}`)) {
|
|
34
|
+
return callback(new Error(`Untrusted launch domain: "${launchDomain}"`));
|
|
35
|
+
}
|
|
36
|
+
callback(null, await getJwksKey(jwksUrl.toString(), kid));
|
|
37
|
+
}
|
|
38
|
+
catch (error) {
|
|
39
|
+
callback(error);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
const verify = (token) => new Promise((resolve, reject) => jsonwebtoken_1.default.verify(token, getKey, {}, (err, payload) => {
|
|
43
|
+
if (err) {
|
|
44
|
+
reject(err);
|
|
45
|
+
}
|
|
46
|
+
else if (typeof payload !== 'object') {
|
|
47
|
+
reject(new Error('received JWT token with unexpected non-JSON payload'));
|
|
48
|
+
}
|
|
49
|
+
else if (!payload.sub) {
|
|
50
|
+
reject(new Error('JWT payload missing sub claim'));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
resolve(payload.sub);
|
|
54
|
+
}
|
|
55
|
+
}));
|
|
56
|
+
return { verify };
|
|
57
|
+
};
|
|
58
|
+
exports.createLaunchVerifier = createLaunchVerifier;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { AccountsGateway } from '../accountsGateway';
|
|
2
|
+
import { AuthProvider } from '../authProvider';
|
|
3
|
+
import { Logger } from '../logger';
|
|
4
|
+
import { LrsGateway } from '.';
|
|
5
|
+
export interface Grade {
|
|
6
|
+
scoreGiven: number;
|
|
7
|
+
scoreMaximum: number;
|
|
8
|
+
comment?: string;
|
|
9
|
+
activityProgress: 'Initialized' | 'Started' | 'inProgress' | 'Submitted' | 'Completed';
|
|
10
|
+
gradingProgress: 'FullyGraded' | 'Pending' | 'PendingManual' | 'Failed' | 'NotReady';
|
|
11
|
+
userId: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const getRegistrationAttemptInfo: (lrs: LrsGateway, registration: string, options?: {
|
|
14
|
+
anyUser?: boolean | undefined;
|
|
15
|
+
} | undefined) => Promise<{
|
|
16
|
+
[key: string]: import("./attempt-utils").ActivityState;
|
|
17
|
+
}>;
|
|
18
|
+
export declare const getCurrentGrade: (services: {
|
|
19
|
+
lrs: LrsGateway;
|
|
20
|
+
ltiAuthProvider: AuthProvider;
|
|
21
|
+
}, registration: string, options?: {
|
|
22
|
+
scoreMaximum?: number | undefined;
|
|
23
|
+
userId?: string | undefined;
|
|
24
|
+
} | undefined) => Promise<Grade | null>;
|
|
25
|
+
export declare const getAllGradesForAssignment: (services: {
|
|
26
|
+
accountsGateway: AccountsGateway;
|
|
27
|
+
lrs: LrsGateway;
|
|
28
|
+
logger: Logger;
|
|
29
|
+
}, registration: string, options?: {
|
|
30
|
+
platformId?: string | undefined;
|
|
31
|
+
scoreMaximum?: number | undefined;
|
|
32
|
+
} | undefined) => Promise<Grade[]>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getAllGradesForAssignment = exports.getCurrentGrade = exports.getRegistrationAttemptInfo = void 0;
|
|
4
|
+
const __1 = require("../..");
|
|
5
|
+
const attempt_utils_1 = require("./attempt-utils");
|
|
6
|
+
const getRegistrationAttemptInfo = async (lrs, registration, options) => {
|
|
7
|
+
const allStatements = await lrs.getAllXapiStatements({ ...options, registration, ensureSync: true });
|
|
8
|
+
// Partition statements for each user
|
|
9
|
+
const statementsPerUser = {};
|
|
10
|
+
allStatements.forEach((statement) => {
|
|
11
|
+
var _a;
|
|
12
|
+
// If we ever support accounts from different statement.actor.account.homePage, this method may need changes
|
|
13
|
+
// (unless we can guarantee the statement.actor.account.name are unique)
|
|
14
|
+
statementsPerUser[_a = statement.actor.account.name] || (statementsPerUser[_a] = []);
|
|
15
|
+
statementsPerUser[statement.actor.account.name].push(statement);
|
|
16
|
+
});
|
|
17
|
+
const result = {};
|
|
18
|
+
for (const [userUuid, userStatements] of Object.entries(statementsPerUser)) {
|
|
19
|
+
result[userUuid] = (0, attempt_utils_1.resolveAttemptInfo)(userStatements);
|
|
20
|
+
}
|
|
21
|
+
return result;
|
|
22
|
+
};
|
|
23
|
+
exports.getRegistrationAttemptInfo = getRegistrationAttemptInfo;
|
|
24
|
+
// generates a payload that can be sent to the LMS, documentation here:
|
|
25
|
+
// lti: http://www.imsglobal.org/spec/lti-ags/v2p0#score-publish-service
|
|
26
|
+
// ltijs: https://cvmcosta.me/ltijs/#/grading
|
|
27
|
+
const getInfoGrade = (info, userId, maxScore) => {
|
|
28
|
+
var _a;
|
|
29
|
+
const completed = info.currentAttemptCompleted;
|
|
30
|
+
const { raw, scaled, max } = ((_a = completed === null || completed === void 0 ? void 0 : completed.result) === null || _a === void 0 ? void 0 : _a.score) || {};
|
|
31
|
+
const scoreMaximum = maxScore !== null && maxScore !== void 0 ? maxScore : 100;
|
|
32
|
+
const scoreGiven = raw && max
|
|
33
|
+
? scoreMaximum / max * raw
|
|
34
|
+
: scaled
|
|
35
|
+
? scaled * scoreMaximum
|
|
36
|
+
: 0;
|
|
37
|
+
return {
|
|
38
|
+
userId,
|
|
39
|
+
activityProgress: completed ? 'Completed' : 'Started',
|
|
40
|
+
gradingProgress: completed ? 'FullyGraded' : 'NotReady',
|
|
41
|
+
scoreMaximum,
|
|
42
|
+
scoreGiven: (0, __1.roundToPrecision)(scoreGiven, -2),
|
|
43
|
+
};
|
|
44
|
+
};
|
|
45
|
+
const getCurrentGrade = async (services, registration, options) => {
|
|
46
|
+
var _a;
|
|
47
|
+
const user = await services.ltiAuthProvider.getUser();
|
|
48
|
+
if (!user) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const infoPerUser = await (0, exports.getRegistrationAttemptInfo)(services.lrs, registration);
|
|
52
|
+
const userInfo = infoPerUser[user.uuid];
|
|
53
|
+
return getInfoGrade(userInfo !== null && userInfo !== void 0 ? userInfo : (0, attempt_utils_1.resolveAttemptInfo)([]), (_a = options === null || options === void 0 ? void 0 : options.userId) !== null && _a !== void 0 ? _a : user.uuid, options === null || options === void 0 ? void 0 : options.scoreMaximum);
|
|
54
|
+
};
|
|
55
|
+
exports.getCurrentGrade = getCurrentGrade;
|
|
56
|
+
const getAllGradesForAssignment = async (services, registration, options) => {
|
|
57
|
+
const infoPerUserUuid = await (0, exports.getRegistrationAttemptInfo)(services.lrs, registration, { anyUser: true });
|
|
58
|
+
const mappedResults = await services.accountsGateway.mapUserUuids(infoPerUserUuid, services.logger, options === null || options === void 0 ? void 0 : options.platformId);
|
|
59
|
+
return mappedResults.map(([userId, userInfo]) => getInfoGrade(userInfo, userId, options === null || options === void 0 ? void 0 : options.scoreMaximum));
|
|
60
|
+
};
|
|
61
|
+
exports.getAllGradesForAssignment = getAllGradesForAssignment;
|
|
@@ -36,8 +36,11 @@ const memorySearchTheBadWay = ({ loadAllDocumentsTheBadWay }) => {
|
|
|
36
36
|
}
|
|
37
37
|
: (x) => x;
|
|
38
38
|
const hasMatch = (0, __1.coerceArray)(field.value).map(coerceValue).some(v => docValues.includes(v));
|
|
39
|
-
|
|
39
|
+
if ((mustMatch === MatchType.Must && !hasMatch) || (mustMatch === MatchType.MustNot && hasMatch)) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
40
42
|
}
|
|
43
|
+
return true;
|
|
41
44
|
};
|
|
42
45
|
if (options.query !== undefined) {
|
|
43
46
|
for (const field of options.fields) {
|