UNPKG

7.62 kBPlain TextView Raw
1/*
2 * Copyright 2021 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
22import { EventEmitter } from "events";
23import { ISessionInfo, IStorage } from "@inrupt/solid-client-authn-core";
24import { injectable } from "tsyringe";
25import { Session } from "./Session";
26import ClientAuthentication from "./ClientAuthentication";
27import { getClientAuthenticationWithDependencies } from "./dependencies";
28
29export interface ISessionManagerOptions {
30 secureStorage?: IStorage;
31 insecureStorage?: IStorage;
32}
33
34export interface ISessionManager {
35 getSession(sessionId?: string): Promise<Session>;
36}
37
38/**
39 * A SessionManager instance can be used to manage all the sessions in an
40 * application, each session being associated with an individual user.
41 */
42@injectable()
43export class SessionManager extends EventEmitter implements ISessionManager {
44 private clientAuthn: ClientAuthentication;
45
46 private sessionRecords: Record<
47 string,
48 { session: Session; logoutCallback: () => unknown }
49 > = {};
50
51 private isInitialized = false;
52
53 private handledIncomingRedirect = false;
54
55 /**
56 * Constructor for the SessionManager object. It is typically used as follows:
57 *
58 * ```typescript
59 * import { SessionManager } from "@inrupt/solid-client-authn-browser";
60 * import customStorage from "./myCustomStorage";
61 *
62 * const sessionManager = new SessionManager({
63 * secureStorage: customStorage
64 * });
65 * ```
66 * See {@link IStorage} for more information on how to define your own storage mechanism.
67 *
68 * @param options Options customizing the behaviour of the SessionManager, namely to store data appropriately.
69 */
70 constructor(options: ISessionManagerOptions = {}) {
71 super();
72 this.clientAuthn = getClientAuthenticationWithDependencies({
73 secureStorage: options.secureStorage,
74 insecureStorage: options.insecureStorage,
75 });
76 }
77
78 private async init(): Promise<void> {
79 if (!this.isInitialized) {
80 await this.handleIncomingRedirect(window.location.href);
81 this.isInitialized = true;
82 }
83 }
84
85 private addNewSessionRecord(session: Session): Session {
86 const logoutCallback = (): void => {
87 this.emit("sessionLogout", session);
88 };
89 session.onLogout(logoutCallback);
90 this.sessionRecords[session.info.sessionId] = {
91 session,
92 logoutCallback,
93 };
94 return session;
95 }
96
97 private getSessionFromCurrentSessionInfo(sessionInfo: ISessionInfo): Session {
98 const sessionRecord = this.sessionRecords[sessionInfo.sessionId];
99 if (sessionRecord) {
100 sessionRecord.session.info.webId = sessionInfo.webId;
101 sessionRecord.session.info.isLoggedIn = sessionInfo.isLoggedIn;
102 return sessionRecord.session;
103 }
104 return this.addNewSessionRecord(
105 new Session({
106 clientAuthentication: this.clientAuthn,
107 sessionInfo,
108 })
109 );
110 }
111
112 /**
113 * @returns all the sessions currently managed by the session manager.
114 */
115 async getSessions(): Promise<Session[]> {
116 await this.init();
117 const sessionInfos = await this.clientAuthn.getAllSessionInfo();
118 return sessionInfos.map((sessionInfo) =>
119 this.getSessionFromCurrentSessionInfo(sessionInfo)
120 );
121 }
122
123 /**
124 * Creates a new session and adds it to the session manager.
125 * If a session ID is not provided then a random UUID will be
126 * assigned as the session ID. If the session of the provided
127 * ID already exists then that session will be returned.
128 *
129 * @param sessionId An optional unique session identifier.
130 * @returns A {@link Session} associated with the given ID.
131 */
132 async getSession(sessionId?: string): Promise<Session> {
133 await this.init();
134 let session: Session;
135 if (sessionId) {
136 const retrievedSessionInfo = await this.clientAuthn.getSessionInfo(
137 sessionId
138 );
139 if (retrievedSessionInfo) {
140 session = this.getSessionFromCurrentSessionInfo(retrievedSessionInfo);
141 } else {
142 session = this.addNewSessionRecord(
143 new Session({ clientAuthentication: this.clientAuthn }, sessionId)
144 );
145 }
146 } else {
147 session = this.addNewSessionRecord(
148 new Session({ clientAuthentication: this.clientAuthn })
149 );
150 }
151 return session;
152 }
153
154 /**
155 * @param sessionId A unique session identifier.
156 * @returns A Promise resolving to true if a session associated with the given ID exists, and false if not.
157 */
158 async hasSession(sessionId: string): Promise<boolean> {
159 await this.init();
160 return (await this.clientAuthn.getSessionInfo(sessionId)) !== undefined;
161 }
162
163 /**
164 * Registers a callback to be called when a session is logged in.
165 *
166 * @param callback a function executed when a session logs in, with the session as a parameter.
167 */
168 onSessionLogin(callback: (session: Session) => unknown): void {
169 this.on("sessionLogin", callback);
170 }
171
172 /**
173 * Registers a callback to be called when a session is logged out.
174 *
175 * @param callback a function executed when a session logs out, with the session as a parameter.
176 */
177 onSessionLogout(callback: (session: Session) => unknown): void {
178 this.on("sessionLogout", callback);
179 }
180
181 /**
182 * Removes a session from the pool managed by the manager. This is typically useful
183 * when a user logs out of the application, so that the number of managed session
184 * is not ever-growing. Note that this specific function **does not log out the session**,
185 * it only removes references to it, so after this call the session will become unreachable.
186 *
187 * @param sessionId A unique session identifier.
188 * @since 0.2.0
189 */
190 detachSession(sessionId: string): void {
191 const sessionRecord = this.sessionRecords[sessionId];
192 if (sessionRecord) {
193 sessionRecord.session.removeListener(
194 "onLogout",
195 sessionRecord.logoutCallback
196 );
197 delete this.sessionRecords[sessionId];
198 }
199 }
200
201 /**
202 * Processes the information sent by the identity provider after
203 * the user has logged in, in order to return a logged in {@link Session}.
204 *
205 * @param url The URL to which the user is being redirected.
206 * @returns The {@link Session} that completed login if the process has been successful.
207 */
208 async handleIncomingRedirect(url: string): Promise<Session | undefined> {
209 const sessionInfo = await this.clientAuthn.handleIncomingRedirect(url);
210 if (sessionInfo) {
211 const session = this.getSessionFromCurrentSessionInfo(sessionInfo);
212 this.emit("sessionLogin", session);
213 session.emit("login");
214 return session;
215 }
216 return undefined;
217 }
218}