UNPKG

7.6 kBPlain TextView Raw
1/*
2 * Copyright 2020 Inrupt Inc.
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal in
6 * the Software without restriction, including without limitation the rights to use,
7 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
8 * Software, and to permit persons to whom the Software is furnished to do so,
9 * subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
15 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
16 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
17 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
18 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
19 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
20 */
21
22/**
23 * @hidden
24 */
25import { EventEmitter } from "events";
26import {
27 ILoginInputOptions,
28 ISessionInfo,
29 IStorage,
30} from "@inrupt/solid-client-authn-core";
31import { v4 } from "uuid";
32import ClientAuthentication from "./ClientAuthentication";
33import { getClientAuthenticationWithDependencies } from "./dependencies";
34
35export interface ISessionOptions {
36 /**
37 * A private storage, unreachable to other scripts on the page. Typically in-memory.
38 */
39 secureStorage: IStorage;
40 /**
41 * A storage where non-sensitive information may be stored, potentially longer-lived than the secure storage.
42 */
43 insecureStorage: IStorage;
44 /**
45 * Details about the current session
46 */
47 sessionInfo: ISessionInfo;
48 /**
49 * An instance of the library core. Typically obtained using `getClientAuthenticationWithDependencies`.
50 */
51 clientAuthentication: ClientAuthentication;
52}
53
54/**
55 * A {@link Session} object represents a user's session on an application. The session holds state, as it stores information enabling acces to private resources after login for instance.
56 */
57export class Session extends EventEmitter {
58 /**
59 * Information regarding the current session.
60 */
61 public readonly info: ISessionInfo;
62
63 private clientAuthentication: ClientAuthentication;
64
65 private tokenRequestInProgress = false;
66
67 /**
68 * Session object constructor. Typically called as follows:
69 *
70 * ```typescript
71 * const session = new Session(
72 * {
73 * clientAuthentication: getClientAuthenticationWithDependencies({})
74 * },
75 * "mySession"
76 * );
77 * ```
78 * @param sessionOptions The options enabling the correct instantiation of
79 * the session. Either both storages or clientAuthentication are required. For
80 * more information, see {@link ISessionOptions}.
81 * @param sessionId A magic string uniquely identifying the session.
82 *
83 */
84 constructor(
85 sessionOptions: Partial<ISessionOptions> = {},
86 sessionId?: string
87 ) {
88 super();
89
90 if (sessionOptions.clientAuthentication) {
91 this.clientAuthentication = sessionOptions.clientAuthentication;
92 } else if (sessionOptions.secureStorage && sessionOptions.insecureStorage) {
93 this.clientAuthentication = getClientAuthenticationWithDependencies({
94 secureStorage: sessionOptions.secureStorage,
95 insecureStorage: sessionOptions.insecureStorage,
96 });
97 } else {
98 this.clientAuthentication = getClientAuthenticationWithDependencies({});
99 }
100
101 if (sessionOptions.sessionInfo) {
102 this.info = {
103 sessionId: sessionOptions.sessionInfo.sessionId,
104 isLoggedIn: false,
105 webId: sessionOptions.sessionInfo.webId,
106 };
107 } else {
108 this.info = {
109 sessionId: sessionId ?? v4(),
110 isLoggedIn: false,
111 };
112 }
113 }
114
115 /**
116 * Triggers the login process. Note that this method will redirect the user away from your app.
117 *
118 * @param options Parameter to customize the login behaviour. In particular, two options are mandatory: `options.oidcIssuer`, the user's identity provider, and `options.redirectUrl`, the URL to which the user will be redirected after logging in their identity provider.
119 * @returns This method should redirect the user away from the app: it does not return anything. The login process is completed by {@linkcode handleIncomingRedirect}.
120 */
121 // Define these functions as properties so that they don't get accidentally re-bound.
122 // Isn't Javascript fun?
123 login = async (options: ILoginInputOptions): Promise<void> => {
124 await this.clientAuthentication.login(this.info.sessionId, {
125 ...options,
126 });
127 };
128
129 /**
130 * Fetches data using available login information. If the user is not logged in, this will behave as a regular `fetch`. The signature of this method is identical to the [canonical `fetch`](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API).
131 *
132 * @param url The URL from which data should be fetched.
133 * @param init Optional parameters customizing the request, by specifying an HTTP method, headers, a body, etc. Follows the [WHATWG Fetch Standard](https://fetch.spec.whatwg.org/).
134 */
135 fetch = async (url: RequestInfo, init?: RequestInit): Promise<Response> => {
136 if (!this.info.isLoggedIn) {
137 // TODO: why does this.clientAuthentication.fetch return throws
138 // ""'fetch' called on an object that does not implement interface Window"
139 // when unauthenticated ?
140 return window.fetch(url, init);
141 }
142 return this.clientAuthentication.fetch(url, init);
143 };
144
145 /**
146 * Logs the user out of the application. This does not log the user out of the identity provider, and should not redirect the user away.
147 */
148 logout = async (): Promise<void> => {
149 await this.clientAuthentication.logout(this.info.sessionId);
150 this.emit("logout");
151 };
152
153 /**
154 * Completes the login process by processing the information provided by the identity provider through redirect.
155 *
156 * @param url The URL of the page handling the redirect, including the query parameters — these contain the information to process the login.
157 */
158 handleIncomingRedirect = async (
159 url: string
160 ): Promise<ISessionInfo | undefined> => {
161 if (this.info.isLoggedIn) {
162 return this.info;
163 }
164 if (this.tokenRequestInProgress) {
165 return undefined;
166 }
167 this.tokenRequestInProgress = true;
168 const sessionInfo = await this.clientAuthentication.handleIncomingRedirect(
169 url
170 );
171 if (sessionInfo) {
172 if (sessionInfo.isLoggedIn) {
173 // The login event can only be triggered **after** the user has been
174 // redirected from the IdP with access and ID tokens.
175 this.emit("login");
176 }
177 this.info.isLoggedIn = sessionInfo.isLoggedIn;
178 this.info.webId = sessionInfo.webId;
179 this.info.sessionId = sessionInfo.sessionId;
180 }
181 this.tokenRequestInProgress = false;
182 return sessionInfo;
183 };
184
185 /**
186 * Register a callback function to be called when a user completes login.
187 *
188 * The callback is called when {@link handleIncomingRedirect} completes successfully.
189 *
190 * @param callback The function called when a user completes login.
191 */
192 onLogin(callback: () => unknown): void {
193 this.on("login", callback);
194 }
195
196 /**
197 * Register a callback function to be called when a user logs out:
198 *
199 * @param callback The function called when a user completes logout.
200 */
201 onLogout(callback: () => unknown): void {
202 this.on("logout", callback);
203 }
204}