├── .gitignore
├── LICENSE
├── README.md
├── __tests__
└── dashportOak.test.js
├── dashports
├── dashportOak.ts
└── templateDashport.ts
├── deprecated
└── v1.0.1
│ ├── dashport.ts
│ ├── sessionManager.test.ts
│ └── sessionManager.ts
├── deps.ts
├── mod.ts
├── templateStrategy.ts
└── types.ts
/.gitignore:
--------------------------------------------------------------------------------
1 | .vscode/
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | *Local authentication and OAuth 2.0 middleware for Deno
*
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | # Features
24 | - Dashport classes that handle authentication and sessions based on the server framework (currently only Oak supported).
25 | - A [local strategy](https://github.com/oslabs-beta/dashport-localstrategy) module.
26 | - Strategy modules that allow developers to use third-party OAuth 2.0
27 | - [x] [Google](https://github.com/oslabs-beta/dashport-googlestrategy)
28 | - [x] [Facebook](https://github.com/oslabs-beta/dashport-facebookstrategy)
29 | - [x] [Github](https://github.com/oslabs-beta/dashport-githubstrategy)
30 | - [x] [LinkedIn](https://github.com/oslabs-beta/dashport-linkedinstrategy)
31 | - [x] [Spotify](https://github.com/oslabs-beta/dashport-spotifystrategy)
32 | - Written in TypeScript.
33 |
34 | # Updates
35 |
36 | ## v1.1.0 to v1.2.0
37 | - Require Application instance for Oak to be passed in when instantiating DashportOak, removing the need for developers to manually call app.use(dashport.initialize).
38 |
39 | ## v1.0.1 to v1.1.0
40 | - Instead of passing in the name of the server framework being used when Dashport is instantiated, Dashport now has different classes for different server frameworks. This is to support better modularity.
41 | - Added a Dashport class for Oak, DashportOak.
42 | - A template Dashport has been provided for any developer to create their own Dashport for a server framework.
43 | - Refactored authenticate to take three arguments instead of one - the strategy, the serializer, and the deserializer to be used vs the name of the strategy.
44 | - Removed add/remove serializer/deserializer methods.
45 | - Merged deserializer's functionality into authenticate.
46 |
47 | # Overview
48 | Dashport is a module that simplifies authentication in Deno. Currently, there is only Dashport support for the server framework Oak, but there is a template Dashport class available for anyone to create their own compatible Dashport.
49 |
50 | Dashport was inspired by [Passport](http://www.passportjs.org/), the golden standard of authentication middleware for Node.js.
51 |
52 | # Getting Started
53 | Below is an example using the Oak server framework. To get started, import Dashport into the server file.
54 |
55 | ```typescript
56 | // 'server.ts' file
57 | import { DashportOak } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
58 | ```
59 |
60 | In the future, additional Dashports can be imported from the same mod.ts file. For example if Express was supported in Deno:
61 |
62 | ```typescript
63 | import { DashportExpress } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
64 | ```
65 |
66 | After importing Dashport, import Application from Oak and instantiate it.
67 |
68 | ```typescript
69 | // 'server.ts' file
70 | import { DashportOak } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
71 | import { Application } from "https://deno.land/x/oak/mod.ts";
72 |
73 | const app = new Application();
74 | ```
75 |
76 | Then instantiate Dashport and pass in the Application instance. This is needed in order to add a Dashport property onto Oak's context object. This specific instance of Dashport will then need to be used when calling the methods authenticate and logOut, so sessions for users can be maintained.
77 |
78 | ```typescript
79 | // 'server.ts' file
80 | import { DashportOak } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
81 | import { Application } from "https://deno.land/x/oak/mod.ts";
82 |
83 | const app = new Application();
84 | const dashport = new DashportOak(app);
85 | ```
86 |
87 | Routes can then be added as needed
88 |
89 | ```typescript
90 | import { DashportOak } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
91 | import { Application, Router } from 'https://deno.land/x/oak/mod.ts';
92 |
93 | const app = new Application();
94 | const router = new Router();
95 |
96 | const dashport = new DashportOak(app);
97 |
98 | // add routes here
99 |
100 | app.use(router.routes());
101 | app.use(router.allowedMethods());
102 |
103 | app.addEventListener("error", (evt) => {
104 | console.log(evt.error);
105 | });
106 |
107 | console.log('running on port', port);
108 | await app.listen({ port });
109 | ```
110 |
111 | In a separate file, add Dashport configurations for a [strategy](#strategies), [serializer](#serializers), and [deserializer](#deserializers). Developers can configure as many strategies, serializers, and deserializers, as they want, as long as they follow the specific rules for each one.
112 |
113 | ```typescript
114 | // 'dashportConfig.ts' file
115 | import GoogleStrategy from 'https://deno.land/x/dashport_google/mod.ts';
116 | import GitHubStrategy from 'https://deno.land/x/dashport_github/mod.ts'
117 |
118 | export const googStrat = new GoogleStrategy({
119 | client_id: 'client-id-here',
120 | client_secret: 'client-secret-here',
121 | redirect_uri: 'http://localhost:8000/privatepage',
122 | response_type: 'code',
123 | scope: 'profile email openid',
124 | grant_type: 'authorization_code',
125 | });
126 |
127 | export const ghStrat = new GitHubStrategy({
128 | client_id: 'client-id-here',
129 | client_secret: 'client-secret-here',
130 | redirect_uri: 'http://localhost:8000/privatepage',
131 | })
132 |
133 | export const serializerA = async (userInfo: any) => {
134 | const serializedId = Math.floor(Math.random() * 1000000000);
135 | userInfo.id = serializedId;
136 |
137 | try {
138 | await exampleDbCreateUpsert(userInfo);
139 | return serializedId;
140 | } catch(err) {
141 | return err;
142 | // or return new Error(err);
143 | }
144 | };
145 |
146 | export const serializerB = async (userInfo: any) => {
147 | ...
148 | }
149 |
150 | export const deserializerA = async (serializedId: (string | number)) => {
151 | try {
152 | const userInfo = await exampleDbFind({ id: serializedId });
153 | return userInfo;
154 | } catch(err) {
155 | return err;
156 | // or return new Error(err);
157 | }
158 | };
159 |
160 | export const deserializerB = async (serializedId: (string | number)) => {
161 | ...
162 | }
163 | ```
164 |
165 | Import these configurations into the server.ts file and Dashport is now ready to authenticate. Dashport's authenticate method acts as middleware, so it can be used like so:
166 |
167 | ```typescript
168 | import { googStrat, serializerA, deserializerA } from './dashportConfig.ts';
169 |
170 | router.get('/privatepage',
171 | dashport.authenticate(googStrat, serializerA, deserializerA),
172 | async (ctx: any, next: any) => {
173 | ctx.response.body = 'This is a private page!';
174 | }
175 | )
176 | ```
177 |
178 | After authentication, Dashport will have created an ID based on the developer's defined serializer and manipulated the data however the developer specified (such as storing user info in a database). The ID then gets stored by Dashport to create a persistent session. Dashport will then invoke the developer's defined deserializer and pass in the ID. The deserializer will take this ID and manipulate it however the developer specified (such as fetching user info from the database). The returned data from the deserializer then gets stored on **ctx.locals** so the next middleware can access it. If an error occurred in the deserializer function and gets returned, the error will be stored on **ctx.locals**.
179 |
180 | ```typescript
181 | router.get('/user-favorites',
182 | dashport.authenticate(googStrat, serializerA, deserializerA),
183 | async (ctx: any, next: any) => {
184 | if (ctx.locals instanceof Error) {
185 | ctx.response.body = 'An Error occurred!';
186 | } else {
187 | const displayName = ctx.locals.displayName;
188 | ctx.response.body = `Welcome ${displayName}!`;
189 | }
190 | }
191 | )
192 | ```
193 |
194 | In order to end a session, a log out button can be routed to an endpoint that calls Dashport's logOut method. Here's an example use with Oak:
195 |
196 | ```typescript
197 | router.get('/log-out',
198 | dashport.logOut,
199 | async (ctx: any, next: any) => {
200 | ctx.response.body = "You've logged out";
201 | }
202 | )
203 | ```
204 |
205 | # Strategies
206 | - Strategies are the modules that can be imported with Dashport to allow third-party OAuth. They specify the authentication logic for a given OAuth service provider.
207 | - Strategy classes need to follow two rules:
208 | 1. Have a router method that ultimately returns an Error or the user information returned by the third-party OAuth in the form of Dashport's defined [AuthData](#authdata) interface. AuthData needs to have a **userInfo** property in the form of [UserProfile](#userprofile) and a **tokenData** property in the form of [TokenData](#tokendata).
209 | 2. When instantiated, take in the options that are needed for the specific third-party OAuth to authenticate.
210 |
211 | ```typescript
212 | const googStrat = new GoogleStrategy({
213 | client_id: 'client-id-here',
214 | client_secret: 'client-secret-here',
215 | redirect_uri: 'http://localhost:8000/privatepage',
216 | response_type: 'code',
217 | scope: 'profile email openid',
218 | grant_type: 'authorization_code',
219 | });
220 | ```
221 |
222 | # Serializers
223 | - After a successful authentication, Dashport will pass the obtained user information to a serializer. It is up to the developer to define serializer functions that specify what to do with user information. User information will be passed in the form of Dashport's defined [AuthData](#authdata) interface.
224 | - Serializer functions need to follow four rules:
225 | 1. Accept one argument: the user data in the form of an object.
226 | 2. Specify how to create a serialized ID.
227 | 3. Specify what the developer wants to do with the user data (e.g. store it in a database).
228 | 4. Return the serialized ID or an Error.
229 |
230 | ```typescript
231 | const serializerA = async (userInfo: any) => {
232 | const serializedId = Math.floor(Math.random() * 1000000000);
233 | userInfo.id = serializedId;
234 |
235 | try {
236 | await exampleDbCreateUpsert(userInfo);
237 | return serializedId;
238 | } catch(err) {
239 | return err;
240 | // or return new Error(err);
241 | }
242 | };
243 | ```
244 |
245 | # Deserializers
246 | - After a successful authentication, Dashport will pass the serialized ID generated from the serializer to the deserializer. The developer should specify what the deserializer should do with the ID and return any data that should be available for access in the next middleware. The shape of the return value is up to the developer. In Oak, if the deserialization is successful, the data returned is stored on **ctx.locals**. If the deserialization is not successful, an Error will be stored on **ctx.locals**.
247 | - Deserializer functions need to follow three rules:
248 | 1. Accept one argument: the serialized ID.
249 | 2. Specify what the developer wants to do with the serialized ID (e.g. fetch user info from a database).
250 | 3. Return the data (e.g. user info) or an Error.
251 |
252 | ```typescript
253 | const deserializerA = async (serializedId: (string | number)) => {
254 | try {
255 | const userInfo = await exampleDbFind({ id: serializedId });
256 | return userInfo;
257 | } catch(err) {
258 | return err;
259 | // or return new Error(err);
260 | }
261 | };
262 | ```
263 |
264 | # Dashport Methods
265 |
266 | ## authenticate
267 | - Exact functionality depends on which Dashport class is being used
268 | - Authenticate is the middleware function that powers Dashport. It takes in three arguments: the instantiation of a strategy to be used, a serializer function, and a deserializer function. Authenticate first checks if a session exists. If a session does not exist, it begins the authentication process for the specified strategy.
269 |
270 | ```typescript
271 | // Oak example
272 | import { DashportOak } from 'https://deno.land/x/dashport@v1.2.1/mod.ts';
273 | import { Application, Router } from 'https://deno.land/x/oak/mod.ts';
274 | import { ghStrat, serializerB, deserializerB } from './dashportConfig.ts';
275 |
276 | const app = new Application();
277 | const router = new Router();
278 |
279 | const dashport = new DashportOak(app);
280 |
281 | app.use(router.routes());
282 | app.use(router.allowedMethods());
283 |
284 | router.get('/secretpage',
285 | dashport.authenticate(ghStrat, serializerB, deserializerB),
286 | async (ctx: any, next: any) => {
287 | ctx.response.body = 'This is a secret page!';
288 | }
289 | )
290 | ```
291 |
292 | ## logOut
293 | - Exact functionality depends on which Dashport class is being used
294 | - logOut is a middleware function that ends a session. If a user logs out and logOut is used, the user will have to reauthenticate. Here is an example use with Oak:
295 |
296 | ```typescript
297 | router.get('/log-out',
298 | dashport.logOut,
299 | async (ctx: any, next: any) => {
300 | ctx.response.body = "You've logged out";
301 | }
302 | )
303 | ```
304 |
305 | # Interfaces
306 |
307 | ## AuthData
308 | When a strategy successfully authenticates a user, the information given by the third-party provider should be returned in the form AuthData. The object should have an optional [tokenData](##tokendata) property and a required userInfo property in the form of [UserProfile](##userprofile). This contains the information for the [authenticate](##authenticate) method to use. The interface for AuthData is as below:
309 |
310 | ```typescript
311 | interface AuthData {
312 | tokenData: TokenData;
313 | userInfo: UserProfile;
314 | }
315 | ```
316 |
317 | ## TokenData
318 | Any relevant token data the developer wishes to receive from the third-party OAuth should be stored in an object with the below interface:
319 |
320 | ```typescript
321 | interface TokenData {
322 | access_token: string;
323 | expires_in: number;
324 | scope: string;
325 | token_type: string;
326 | id_token: string;
327 | }
328 | ```
329 |
330 | ## UserProfile
331 | Since every OAuth provider returns information in different names and shapes, it is up to each strategy to conform the data returned into Dashport's defined UserProfile interface. This should contain all data the developer wishes to use.
332 |
333 | ```typescript
334 | export interface UserProfile {
335 | provider: string;
336 | providerUserId: string;
337 | displayName?: string;
338 | name?: {
339 | familyName?: string;
340 | givenName?: string;
341 | middleName?: string;
342 | };
343 | emails?: Array;
344 | }
345 | ```
346 |
347 | # Stretch Features
348 | - Add more strategies.
349 | - Add support for other server frameworks.
350 |
351 | # How To Contribute
352 | We would love to hear your experience and get your feedback on our modules. Feel free to send us any issues, concerns, or suggestions, in our Issues section, or simply contact us through LinkedIn.
353 |
354 | # Developers
355 |
356 | [*Dashport website*](https://www.dashport.org/)
357 |
358 | Alex Nance :: [LinkedIn](https://www.linkedin.com/in/balexandernance/) | [GitHub](https://github.com/BAlexanderNance)
359 |
360 | Alvin Cheng :: [LinkedIn](https://www.linkedin.com/in/alvin-cheng/) | [GitHub](https://github.com/alcheng005)
361 |
362 | Edward Deng :: [LinkedIn](https://www.linkedin.com/in/edwarddeng-/) | [GitHub](https://github.com/ed-deng)
363 |
364 | Sam Portelance :: [LinkedIn](https://www.linkedin.com/in/sportelance/) | [GitHub](https://github.com/sportelance)
365 |
366 | Wei Huang :: [LinkedIn](https://www.linkedin.com/in/wei-waye-huang/) | [GitHub](https://github.com/waye-huang)
367 |
368 | # License
369 | This project is licensed under the [MIT License](https://github.com/oslabs-beta/DraQLa/blob/main/LICENSE)
370 |
371 |
--------------------------------------------------------------------------------
/__tests__/dashportOak.test.js:
--------------------------------------------------------------------------------
1 | import { Application, assertEquals, assertThrows, assertThrowsAsync } from "../deps.ts";
2 | import DashportOak from '../dashports/dashportOak.ts';
3 |
4 | // Mock class and functions
5 | class TestStrat {
6 | async router(ctx, next) {
7 | if (ctx.state.testing = 'user') {
8 | return {
9 | authData: {
10 | access_token: 'AccessToken000'
11 | },
12 | userInfo: {
13 | provider: 'test',
14 | providerUserId: 'ID12345'
15 | }
16 | }
17 | }
18 |
19 | return new Error('ERROR: This is an Error');
20 | }
21 | }
22 |
23 | function fakeSerializer(userInfo) {
24 | return 'ABCDEF';
25 | }
26 |
27 | function fakeDeserializer(serializedId) {
28 | return {
29 | firstName: 'Hello',
30 | lastName: 'World'
31 | };
32 | }
33 |
34 | const fakeNext = () => 'fakeNext was invoked';
35 |
36 |
37 |
38 | // INITIALIZE TEST
39 | Deno.test({
40 | name: "initialize should throw if ctx.state is undefined.",
41 | fn() {
42 | const app = new Application();
43 | const fakeOakCtx = {
44 | app: {},
45 | cookies: {},
46 | request: {},
47 | respond: {},
48 | response: {},
49 | socket: {},
50 | assert: () => 1,
51 | send: () => 2,
52 | sendEvents: () => 3,
53 | throw: () => 4,
54 | upgrade: () => 5,
55 | params: {}
56 | }
57 |
58 | const testDpOak = new DashportOak(app);
59 | assertThrowsAsync(async () => await testDpOak.initialize(fakeOakCtx, fakeNext));
60 | },
61 | });
62 |
63 | Deno.test({
64 | name: "initialize should add a _dashport object onto ctx.state.",
65 | async fn() {
66 | const app = new Application();
67 | const fakeOakCtx = {
68 | app: {},
69 | cookies: {},
70 | request: {},
71 | respond: {},
72 | response: {},
73 | socket: {},
74 | assert: () => 1,
75 | send: () => 2,
76 | sendEvents: () => 3,
77 | throw: () => 4,
78 | upgrade: () => 5,
79 | params: {},
80 | state: {}
81 | }
82 |
83 | const testDpOak = new DashportOak(app);
84 | await testDpOak.initialize(fakeOakCtx, fakeNext);
85 |
86 | assertEquals(Object.keys(fakeOakCtx.state)[0], '_dashport');
87 | assertEquals(Object.values(fakeOakCtx.state)[0], {});
88 | },
89 | });
90 |
91 |
92 |
93 | // AUTHENTICATE TESTS
94 | Deno.test({
95 | name: "authenticate should throw an error if three arguments are not passed.",
96 | fn() {
97 | const app = new Application();
98 | const testDpOak = new DashportOak(app);
99 |
100 | assertThrows(() => testDpOak.authenticate({test: 'foobar'}));
101 | assertThrows(() => testDpOak.authenticate({test: 'foobar'}, fakeSerializer));
102 | assertThrows(() => testDpOak.authenticate(fakeSerializer, fakeDeserializer));
103 | assertThrows(() => testDpOak.authenticate(fakeDeserializer));
104 | },
105 | });
106 |
107 |
108 | // Strategy tests
109 | Deno.test({
110 | name: "authenticate should throw an error if strategy is not an object.",
111 | fn() {
112 | const app = new Application();
113 | const testDpOak = new DashportOak(app);
114 |
115 | assertThrows(() => testDpOak.authenticate('hello world', fakeSerializer, fakeDeserializer));
116 | },
117 | });
118 |
119 | Deno.test({
120 | name: "authenticate should throw an error if strategy does not have a router property.",
121 | fn() {
122 | const app = new Application();
123 | const testDpOak = new DashportOak(app);
124 |
125 | assertThrows(() => testDpOak.authenticate({ test: 'hello world' }, fakeSerializer, fakeDeserializer));
126 | },
127 | });
128 |
129 | Deno.test({
130 | name: "authenticate should throw an error if strategy's router property is not a function.",
131 | fn() {
132 | const app = new Application();
133 | const testDpOak = new DashportOak(app);
134 |
135 | assertThrows(() => testDpOak.authenticate({ router: 'hello world' }, fakeSerializer, fakeDeserializer));
136 | },
137 | });
138 |
139 |
140 | // Serializer tests
141 | Deno.test({
142 | name: "authenticate should throw an error if serializer is not a function.",
143 | fn() {
144 | const app = new Application();
145 | const testDpOak = new DashportOak(app);
146 |
147 | assertThrows(() => testDpOak.authenticate(new TestStrat(), 'dashport', fakeDeserializer));
148 | },
149 | });
150 |
151 | Deno.test({
152 | name: "authenticate should throw an error if serializer does not take exactly 1 parameter.",
153 | fn() {
154 | const app = new Application();
155 | const testDpOak = new DashportOak(app);
156 |
157 | assertThrows(() => testDpOak.authenticate(new TestStrat(), () => 1, fakeDeserializer));
158 | assertThrows(() => testDpOak.authenticate(new TestStrat(), (test, testing) => 1, fakeDeserializer));
159 | },
160 | });
161 |
162 |
163 | // Deserializer tests
164 | Deno.test({
165 | name: "authenticate should throw an error if deserializer is not a function.",
166 | fn() {
167 | const app = new Application();
168 | const testDpOak = new DashportOak(app);
169 |
170 | assertThrows(() => testDpOak.authenticate(new TestStrat(), fakeSerializer, 'dashport'));
171 | },
172 | });
173 |
174 | Deno.test({
175 | name: "authenticate should throw an error if deserializer does not take exactly 1 parameter.",
176 | fn() {
177 | const app = new Application();
178 | const testDpOak = new DashportOak(app);
179 |
180 | assertThrows(() => testDpOak.authenticate(new TestStrat(), fakeSerializer, () => 'foobar'));
181 | assertThrows(() => testDpOak.authenticate(new TestStrat(), fakeSerializer, (id, testing) => 'foobar'));
182 | },
183 | });
184 |
185 |
186 | // Invocation of middleware tests
187 | Deno.test({
188 | name: "authenticate's middleware should throw an error if _dashport object does not exist on ctx.state.",
189 | fn() {
190 | const app = new Application();
191 | const testDpOak = new DashportOak(app);
192 | const fakeOakCtx = {
193 | app: {},
194 | cookies: {},
195 | request: {},
196 | respond: {},
197 | response: {},
198 | socket: {},
199 | state: {},
200 | assert: () => 1,
201 | send: () => 2,
202 | sendEvents: () => 3,
203 | throw: () => 4,
204 | upgrade: () => 5,
205 | params: {}
206 | }
207 |
208 | assertThrowsAsync(async () => await testDpOak.authenticate(new TestStrat(), fakeSerializer, fakeDeserializer)(fakeOakCtx, fakeNext));
209 | },
210 | });
211 |
212 | Deno.test({
213 | name: "If ctx.state._dashport.session equals dashport._sId, authenticate should store the deserialized user info on ctx.locals and invoke the 'next' function.",
214 | async fn() {
215 | const app = new Application();
216 | const testDpOak = new DashportOak(app);
217 | const fakeOakCtx = {
218 | app: {},
219 | cookies: {},
220 | request: {},
221 | respond: {},
222 | response: {},
223 | socket: {},
224 | state: {
225 | _dashport: {
226 | session: '123456789'
227 | }
228 | },
229 | assert: () => 1,
230 | send: () => 2,
231 | sendEvents: () => 3,
232 | throw: () => 4,
233 | upgrade: () => 5,
234 | params: {}
235 | }
236 |
237 | testDpOak._sId = '123456789';
238 |
239 | assertEquals(await testDpOak.authenticate(new TestStrat(), fakeSerializer, fakeDeserializer)(fakeOakCtx, fakeNext), 'fakeNext was invoked');
240 | assertEquals(fakeOakCtx.locals, { firstName: 'Hello', lastName: 'World' });
241 | },
242 | });
243 |
244 | Deno.test({
245 | name: "If there is no session or session ID does not match, authenticate should call the strategy's router method and begin authentication.",
246 | async fn(){
247 | const app = new Application();
248 | const testDpOak = new DashportOak(app);
249 | const fakeOakCtx = {
250 | app: {},
251 | cookies: {},
252 | request: {},
253 | respond: {},
254 | response: {},
255 | socket: {},
256 | state: {
257 | _dashport: {},
258 | testing: 'user'
259 | },
260 | assert: () => 1,
261 | send: () => 2,
262 | sendEvents: () => 3,
263 | throw: () => 4,
264 | upgrade: () => 5,
265 | params: {}
266 | }
267 |
268 | // if authenticate was successful, the 'next' function should be invoked
269 | assertEquals(await testDpOak.authenticate(new TestStrat(), fakeSerializer, fakeDeserializer)(fakeOakCtx, fakeNext), 'fakeNext was invoked');
270 | // after authenticate was successful, the serialized ID should now exist on fakeOakCtx.state._dashport.session
271 | assertEquals(fakeOakCtx.state._dashport.session, 'ABCDEF');
272 | // after authenticate was successful, the serialized ID should now exist on testDpOak._sId
273 | assertEquals(testDpOak._sId, 'ABCDEF');
274 | // after authenticate was successful, deserialized info should exist on ctx.locals
275 | assertEquals(fakeOakCtx.locals, { firstName: 'Hello', lastName: 'World' });
276 | },
277 | });
278 |
279 |
280 |
281 | // LOGOUT TEST
282 | Deno.test({
283 | name: "logOut should delete the session property on ctx.state._dashport",
284 | async fn() {
285 | const app = new Application();
286 | const testDpOak = new DashportOak(app);
287 | const fakeOakCtx = {
288 | app: {},
289 | cookies: {},
290 | request: {},
291 | respond: {},
292 | response: {},
293 | socket: {},
294 | state: {
295 | _dashport: {
296 | session: '123'
297 | }
298 | },
299 | assert: () => 1,
300 | send: () => 2,
301 | sendEvents: () => 3,
302 | throw: () => 4,
303 | upgrade: () => 5,
304 | params: {}
305 | }
306 |
307 | assertEquals(fakeOakCtx.state._dashport.session, '123');
308 | await testDpOak.logOut(fakeOakCtx, fakeNext);
309 | assertEquals(fakeOakCtx.state._dashport.session, undefined);
310 | }
311 | });
312 |
--------------------------------------------------------------------------------
/dashports/dashportOak.ts:
--------------------------------------------------------------------------------
1 | import { Application } from "../deps.ts";
2 | import { AuthData, OakContext, Strategy } from '../types.ts';
3 |
4 | class DashportOak {
5 | private _sId: (string | number) = '';
6 |
7 | constructor(app: Application) {
8 | const self = this;
9 | app.use(self.initialize);
10 | }
11 |
12 | /**
13 | * Creates a _dashport object that will persist across multiple HTTP requests.
14 | *
15 | * @param {OakContext} ctx - Oak's 'context' object
16 | * @param {*} next - Oak's 'next' function
17 | */
18 | public async initialize(ctx: OakContext, next: any) {
19 | // '?.' - optional chaining
20 | if (ctx?.state === undefined) {
21 | throw new Error('ERROR in initialize: ctx object and \'state\' property needs to exist when using Oak. Use app.use(dashport.initialize) not app.use(dashport.initialize()).');
22 | }
23 |
24 | // if a _dashport object on ctx.state does not exist, create one. ctx.state
25 | // will persist across different requests
26 | if (ctx.state._dashport === undefined) {
27 | ctx.state._dashport = {};
28 | }
29 |
30 | return await next();
31 | }
32 |
33 | /**
34 | * In OAuth, there is a lot of back and forth communication between the
35 | * client, the app, and the OAuth provider, when a user want to sign in.
36 | * Tokens are sent back and forth and user data gets sent back at the end.
37 | * The middleware function that is returned from the 'authenticate' method
38 | * bundles up this functionality by executing code depending if a user has
39 | * been authenticated or not. This allows one method to be used for a seamless
40 | * OAuth process.
41 | *
42 | * Authenticate method takes in a 'strategy', 'serializer', and 'deserializer'.
43 | *
44 | * Strategies are classes that handle the OAuth process for a specific OAuth
45 | * provider. A 'strategy' will be an instantiation of a new Strategy class
46 | * such as GoogleStrategy. When strategies are instantiated, options must be
47 | * passed in such as a client ID, client secret, redirect URI, etc., for the
48 | * strategy class to use. ALL strategies made for Dashport MUST have a 'router'
49 | * method that on successful authentication, returns an authData object with a
50 | * userInfo property in the form of UserProfile.
51 | *
52 | * Serializers are functions that need to have the following functionalities:
53 | * 1. The serializer function needs to take in one argument which will be
54 | * the user data in the form of an object. This will be in the shape of
55 | * the UserProfile interface
56 | * 2. The serializer function should to specify how to create a serialized ID
57 | * 3. The serializer function needs to specify what the developer wants to
58 | * do with the user data (e.g. store in a database)
59 | * 4. The serializer function should return the serialized ID or an error
60 | *
61 | * Deserializers are functions that need to have the following functionalities:
62 | * 1. The deserializer function needs to take in one parameter which will be
63 | * the serialized ID
64 | * 2. The deserializer function needs to specify what the developer wants to
65 | * do with the serialized ID to obtain user info (e.g. fetch the userData
66 | * from a database)
67 | * 3. The deserializer function needs to return the user info or an Error
68 | *
69 | * EXAMPLE: Using dashport.authenticate as a middleware in Oak
70 | * import * as dpSettings from './dashport.settings.ts';
71 | *
72 | * router.get('/test',
73 | * dashport.authenticate(
74 | * dpSettings.googleStrat,
75 | * dpSettings.serializerA,
76 | * dpSettings.deserializerA
77 | * ),
78 | * (ctx, next) => {
79 | * ctx.response.body = 'Hello World';
80 | * }
81 | * );
82 | *
83 | * @param {Strategy} strategy - An instance of a strategy to be used
84 | * @param {Function} serializer - The function that will take in user info and
85 | * output an ID
86 | * @param {Function} deserializer - The function that will take in an ID and
87 | * output user info
88 | * @returns {Function} The function that will be the middleware
89 | */
90 | public authenticate(strategy: Strategy, serializer: Function, deserializer: Function): Function {
91 | // below error checks will only run once when code is first compiled
92 |
93 | // arguments error check
94 | if (strategy === undefined || serializer === undefined || deserializer === undefined) {
95 | throw new Error("ERROR in authenticate: authenticate must be passed 'strategy', 'serializer', and 'deserializer' values.");
96 | }
97 |
98 | // strategy error checks
99 | if (typeof(strategy) !== 'object') {
100 | throw new Error('ERROR in authenticate: strategy must be an object (should be a class).');
101 | }
102 | if (strategy.router === undefined) {
103 | throw new Error('ERROR in authenticate: strategy must have a \'router\' property.');
104 | }
105 | if (typeof(strategy.router) !== 'function') {
106 | throw new Error('ERROR in authenticate: strategy\'s \'router\' property must be a function (should be method on strategy class).');
107 | }
108 |
109 | // serializer error checks
110 | if (typeof serializer !== 'function') {
111 | throw new Error('ERROR in authenticate: serializer must be a function.');
112 | }
113 | if (serializer.length !== 1) {
114 | throw new Error('ERROR in authenticate: serializer function must take in exactly 1 parameter (which will be an object containing the user info).');
115 | }
116 |
117 | // deserializer error checks
118 | if (typeof deserializer !== 'function') {
119 | throw new Error('ERROR in authenticate: deserializer must be a function.');
120 | }
121 | if (deserializer.length !== 1) {
122 | throw new Error('ERROR in authenticate: deserializer function must take in exactly 1 parameter (which will be the ID generated from the serializer).');
123 | }
124 |
125 | return async (ctx: OakContext, next: any) => {
126 | if (ctx.state._dashport === undefined) {
127 | throw new Error('ERROR in authenticate: Dashport needs to be initialized first with app.use(dashport.initialize).');
128 | }
129 |
130 | if (ctx.state._dashport.session) {
131 | if (this._sId !== '' && ctx.state._dashport.session === this._sId) {
132 | // if checks pass, then user has been authenticated
133 | const userData: any = await deserializer(this._sId);
134 | ctx.locals = userData;
135 |
136 | return await next();
137 | }
138 | }
139 |
140 | // if above check is not passed, user must be authenticated (again), so
141 | // call the requested strategy's 'router' method. All strategies must have
142 | // a 'router' method. authData must contain a userInfo property in the
143 | // form of UserProfile
144 | const authData: (AuthData | undefined) = await strategy.router(ctx, next);
145 |
146 | // check if authData is not undefined (it will be undefined in the first
147 | // step of the OAuth process when user gets redirected to OAuth provider
148 | // to log in). If it is not undefined, that means user has been
149 | // authenticated by OAuth. Can begin session management process
150 | if (authData !== undefined) {
151 | // obtain a serialized ID by using the serializer function
152 | const serializedId: (string | number | Error) = await serializer(authData.userInfo);
153 |
154 | // if serializedId is an Error, throw it to be caught
155 | if (serializedId instanceof Error) throw serializedId;
156 |
157 | if (typeof serializedId !== 'string' && typeof serializedId !== 'number') {
158 | throw new Error('ERROR in authenticate: serializedId returned from serializer must be a string, number, or an Error.')
159 | }
160 |
161 | // store the serialized ID onto a session object on _dashport and onto
162 | // this instance of Dashport's _sId
163 | this.logIn(ctx, serializedId);
164 |
165 | // invoke the deserializer, passing in the serialized ID, to get the
166 | // data in the structure the developer wants. Store the user data on
167 | // ctx.locals for the next middleware to access
168 | const userData: any = await deserializer(this._sId);
169 | ctx.locals = userData;
170 |
171 | return await next();
172 | }
173 | }
174 | }
175 |
176 | /**
177 | * Adds the serializedId obtained in authenticate method to the _dashport
178 | * object and to the _sId property of this instance of Dashport. This will
179 | * help with session management.
180 | *
181 | * @param {OakContext} ctx - the object that contains the _dashport object
182 | * @param {(string|number)} serializedId - the serialized ID generated in
183 | * authenticate by the serializer function
184 | */
185 | private logIn(ctx: OakContext, serializedId: (string | number)): void {
186 | if (ctx.state._dashport) {
187 | ctx.state._dashport.session = serializedId;
188 | this._sId = serializedId;
189 | } else {
190 | throw new Error('ERROR in logIn: ctx.state._dashport does not exist. Please use app.use(dashport.initialize).');
191 | }
192 | }
193 |
194 | /**
195 | * Deletes any session info that exists on the _dashport object. This will
196 | * cause the user to go through the OAuth process on the next authenticate call.
197 | *
198 | * @param {OakContext} ctx - the object that contains the _dashport object
199 | * @param {*} next - Oak's 'next' function
200 | */
201 | public async logOut(ctx: OakContext, next: any) {
202 | delete ctx.state._dashport.session;
203 | return await next();
204 | }
205 | }
206 |
207 | export default DashportOak;
208 |
--------------------------------------------------------------------------------
/dashports/templateDashport.ts:
--------------------------------------------------------------------------------
1 | class TemplateDashport {
2 | private _sId: (string | number) = '';
3 |
4 | /**
5 | * Usually a middleware function.
6 | *
7 | * Creates a _dashport object that will persist across multiple HTTP requests.
8 | *
9 | * Parameters will be specific to the server framework (e.g. Oak will be ctx
10 | * and next)
11 | * Output will usually be invocation of a 'next' function
12 | */
13 | public initialize() {
14 | // check if _dashport exists. If it doesn't, create one
15 | // invoke 'next'
16 | }
17 |
18 | /**
19 | * In OAuth, there is a lot of back and forth communication between the
20 | * client, the app, and the OAuth provider, when a user want to sign in.
21 | * Tokens are sent back and forth and user data gets sent back at the end.
22 | * The middleware function that is returned from the 'authenticate' method
23 | * bundles up this functionality by executing code depending if a user has
24 | * been authenticated or not. This allows one method to be used for a seamless
25 | * OAuth process.
26 | *
27 | * Authenticate method takes in a 'strategy', 'serializer', and 'deserializer'.
28 | *
29 | * Strategies are classes that handle the OAuth process for a specific OAuth
30 | * provider. A 'strategy' will be an instantiation of a new Strategy class
31 | * such as GoogleStrategy. When strategies are instantiated, options must be
32 | * passed in such as a client ID, client secret, redirect URI, etc., for the
33 | * strategy class to use. ALL strategies made for Dashport MUST have a 'router'
34 | * method that on successful authentication, returns an authData object with a
35 | * userInfo property in the form of UserProfile.
36 | *
37 | * Serializers are functions that need to have the following functionalities:
38 | * 1. The serializer function needs to take in one argument which will be
39 | * the user data in the form of an object. This will be in the shape of
40 | * the UserProfile interface
41 | * 2. The serializer function needs to specify what the developer wants to
42 | * do with the user data (e.g. store in a database)
43 | * 3. The serializer function should to specify how to create a serialized ID
44 | * 4. The serializer function should return the serialized ID or an error
45 | *
46 | * Deserializers are functions that need to have the following functionalities:
47 | * 1. The deserializer function needs to take in one parameter which will be
48 | * the serialized ID
49 | * 2. The deserializer function needs to specify what the developer wants to
50 | * do with the serialized ID to obtain user info (e.g. fetch user's data
51 | * from a database)
52 | * 3. The deserializer function needs to return the user info or an Error
53 | *
54 | * EXAMPLE: Using dashport.authenticate as a middleware in Oak
55 | * import * as dpSettings from './dashport.settings.ts';
56 | *
57 | * router.get('/test',
58 | * dashport.authenticate(
59 | * dpSettings.googleStrat,
60 | * dpSettings.serializerA,
61 | * dpSettings.deserializerA
62 | * ),
63 | * (ctx, next) => {
64 | * ctx.response.body = 'Hello World';
65 | * }
66 | * );
67 | *
68 | * @param strategy - An instance of a strategy to be used
69 | * @param {Function} serializer - The function that will take in user info and
70 | * output an ID
71 | * @param {Function} deserializer - The function that will take in an ID and
72 | * output user info
73 | * @returns Output should be the function that will be the middleware
74 | */
75 | public authenticate(strategy, serializer: Function, deserializer: Function) {
76 | // below error checks will only run once when code is first compiled
77 |
78 | // arguments error check
79 | if (strategy === undefined || serializer === undefined || deserializer === undefined) {
80 | throw new Error("ERROR in authenticate: authenticate must be passed 'strategy', 'serializer', and 'deserializer' values.");
81 | }
82 |
83 | // strategy error checks
84 | if (typeof(strategy) !== 'object') {
85 | throw new Error('ERROR in authenticate: strategy must be an object (should be a class).');
86 | }
87 | if (strategy.router === undefined) {
88 | throw new Error('ERROR in authenticate: strategy must have a \'router\' property.');
89 | }
90 | if (typeof(strategy.router) !== 'function') {
91 | throw new Error('ERROR in authenticate: strategy\'s \'router\' property must be a function (should be method on strategy class).');
92 | }
93 |
94 | // serializer error checks
95 | if (typeof serializer !== 'function') {
96 | throw new Error('ERROR in authenticate: serializer must be a function.');
97 | }
98 | if (serializer.length !== 1) {
99 | throw new Error('ERROR in authenticate: serializer function must take in exactly 1 parameter (which will be an object containing the user info).');
100 | }
101 |
102 | // deserializer error checks
103 | if (typeof deserializer !== 'function') {
104 | throw new Error('ERROR in authenticate: deserializer must be a function.');
105 | }
106 | if (deserializer.length !== 1) {
107 | throw new Error('ERROR in authenticate: deserializer function must take in exactly 1 parameter (which will be the ID generated from the serializer).');
108 | }
109 |
110 | // return an async middleware function that fits the server framework being used
111 | // check if _dashport object exists. If it doesn't, throw an error
112 |
113 | // check if session property exists on _dashport. If it does, check if its
114 | // value equals this._sId and that this._sId is not ''. If true user has
115 | // already been authenticated, so invoke deserializer and store info on
116 | // ctx.local for next middleware, and call 'next'
117 |
118 | // if above check is not passed, user must be authenticated (again), so
119 | // call the requested strategy's 'router' method. authData must contain
120 | // a userInfo property in the form of UserProfile
121 | // ex for Oak:
122 | // const authData: (AuthData | undefined) = await strategy.router(ctx, next);
123 |
124 | // check if authData is not undefined (it will be undefined in the first
125 | // step of the OAuth process when user gets redirected to OAuth provider
126 | // to log in). If it is not undefined, that means user has been
127 | // authenticated by OAuth. Can begin session management process
128 | // obtain a serialized ID by using the serializer function. It should be
129 | // a string or a number
130 |
131 | // store the serialized ID onto a session object on _dashport and onto
132 | // this instance of Dashport's _sId
133 |
134 | // invoke the deserializer, passing in the serialized ID, to get the
135 | // data in the structure the developer wants. Store the info on
136 | // ctx.locals for the next middleware to access
137 |
138 | // invoke 'next'
139 | }
140 |
141 | /**
142 | * Adds the serializedId obtained in authenticate method to the _dashport
143 | * object and to the _sId property of this instance of Dashport. This will
144 | * help with session management.
145 | *
146 | * One parameter will be the object containing the _dashport object
147 | * @param {(string|number)} serializedId - the serialized ID generated in
148 | * authenticate by the serializer function
149 | */
150 | private logIn(serializedId: (string | number)): void {
151 | // check if the _dashport object exists
152 | // if it does, add serializedId as a value onto _dashport and as the value
153 | // for this._sId
154 | // if it doesn't, throw an error since _dashport needs to exist
155 | }
156 |
157 | /**
158 | * Deletes any session info that exists on the _dashport object. This will
159 | * cause the user to go through the OAuth process on the next authenticate call.
160 | *
161 | * Parameters will be specific to the server framework (e.g. Oak will be ctx
162 | * and next)
163 | */
164 | public logOut() {
165 | // delete the session information from _dashport
166 | // invoke 'next'
167 | }
168 | }
169 |
170 | export default TemplateDashport;
171 |
--------------------------------------------------------------------------------
/deprecated/v1.0.1/dashport.ts:
--------------------------------------------------------------------------------
1 | import { OakContext, Translators, Strategies, Strategy } from './types.ts';
2 | import SessionManager from './sessionManager.ts';
3 |
4 | class Dashport {
5 | private _serializers: Translators = {};
6 | private _deserializers: Translators = {};
7 | private _strategies: Strategies = {};
8 | private _framework: string;
9 | private _sm: SessionManager;
10 | // public since _sId needs to be accessed by SessionManager but _ since a
11 | // developer should not access it
12 | public _sId: string = '';
13 | public initialize: any;
14 | // Note to help clear any confusion - the purpose of deserialize is NOT to
15 | // remove the serializedId. High-level explanation:
16 | // serialize takes in user info and outputs a serializedId
17 | // deserialize takes in a serializedId and outputs user info
18 | public deserialize: any;
19 | public logOut: Function;
20 |
21 | constructor(frmwrk: string) {
22 | frmwrk = frmwrk.toLowerCase();
23 | this._framework = frmwrk;
24 | this._sm = new SessionManager(frmwrk);
25 | this.logOut = this._sm.logOut;
26 | this.initialize = this._initializeDecider(frmwrk);
27 | this.deserialize = this._deserializeDecider(frmwrk);
28 | this.authenticate = this.authenticate.bind(this);
29 | }
30 |
31 | /**
32 | * Takes in a framework and returns a function that will become dashport's
33 | * initialize method. _initializeDecider runs inside the constructor of a new
34 | * Dashport instance.
35 | *
36 | * TODO: Add other frameworks
37 | *
38 | * @param {string} framework - The server framework that will be used
39 | * @returns {*} The async function that will be dashport's intialize method
40 | */
41 | private _initializeDecider(framework: string) {
42 | if (framework === 'oak') {
43 | return async (ctx: OakContext, next: any) => {
44 | if (ctx.state === undefined) {
45 | throw new Error('ERROR in initialize: \'state\' property needs to exist on context object of Oak. Use app.use(dashport.initialize) not app.use(dashport.initialize()).');
46 | }
47 |
48 | // if the _dashport property on ctx.state does not exist, create one.
49 | // ctx.state will persist across requests
50 | if (ctx.state._dashport === undefined) {
51 | ctx.state._dashport = {};
52 | }
53 |
54 | return await next();
55 | }
56 | }
57 |
58 | throw new Error('ERROR in constructor of Dashport: Name of framework passed in is not supported.');
59 | }
60 |
61 | /**
62 | * Takes in a strategy name that should exist on this._strategies. The
63 | * strategy will need to be have been added by the developer.
64 | *
65 | * Authenticate returns an async function depending on the framework being
66 | * used, that will serve as a middleware function.
67 | *
68 | * In OAuth, there is a lot of back and forth communication between the
69 | * client, the app, and the OAuth provider, when a user want to sign in.
70 | * Tokens are sent back and forth and user data gets sent back at the end.
71 | * The middleware function that is returned from the 'authenticate' method
72 | * bundles up this functionality by executing code depending if a user has
73 | * been authenticated or not. This allows one method to be used for a seamless
74 | * OAuth process.
75 | *
76 | * EXAMPLE: Adding a strategy
77 | *
78 | * dashport.addStrategy('google', GoogleStrategy);
79 | *
80 | * EXAMPLE: Using dashport.authenticate as a middleware in Oak
81 | *
82 | * router.get('/test',
83 | * dashport.authenticate('google'),
84 | * (ctx: OakContext, next: any) => {
85 | * ctx.response.body = 'Hello World';
86 | * }
87 | * );
88 | *
89 | * TODO: Add optional parameter for options in case developers want to have
90 | * different strategy options for a particular route.
91 | *
92 | * @param {string} stratName - The name of a strategy that was added
93 | * @returns {Function} The middleware function (differs depending on server framework)
94 | */
95 | public authenticate(stratName: string): Function {
96 | const self: Dashport = this;
97 |
98 | if (this._strategies[stratName] === undefined) {
99 | throw new Error('ERROR in authenticate: This strategy name has not been specified for use.');
100 | }
101 |
102 | if (this._framework === 'oak') {
103 | return async (ctx: OakContext, next: any) => {
104 | if (ctx.state._dashport === undefined) {
105 | throw new Error('ERROR in authenticate: Dashport needs to be initialized first with app.use(dashport.initialize).');
106 | }
107 |
108 | // check if a session object exists (created by SessionManager.logIn).
109 | // If it exists, check if the session ID matches. If it does, user has
110 | // already been authenticated, so user can go to next middleware
111 | if (ctx.state._dashport.session) {
112 | if (ctx.state._dashport.session === self._sId) {
113 | return await next();
114 | }
115 | }
116 |
117 | // if above check is not passed, user must be authenticated (again), so
118 | // call the requested strategy's 'router' method. authData must contain
119 | // a userInfo property in the form of UserProfile
120 | const authData: any = await self._strategies[stratName].router(ctx, next);
121 |
122 | if (authData !== undefined) {
123 | // serializedId will be obtained by calling SessionManager's serialize
124 | // function, which will invoke the serializer(s) the developer specified.
125 | // serializedId is type 'any' because lefthand side of instanceof must
126 | // be type 'any'
127 | const serializedId: any = await self._sm.serialize(self._serializers, authData.userInfo);
128 |
129 | // if serializedId is an Error, throw it to be caught
130 | if (serializedId instanceof Error) throw serializedId;
131 | if (typeof serializedId !== 'string' && typeof serializedId !== 'number') {
132 | throw new Error('ERROR in authenticate: serializedId returned from serializer must be a string, number, or an Error.')
133 | }
134 |
135 | // use SessionManager's logIn method to create a session object on
136 | // ctx.state._dashport and to assign serializedId to the _sId property
137 | // of this instance of Dashport
138 | self._sm.logIn(ctx, self, serializedId);
139 |
140 | return await next();
141 | }
142 | }
143 | }
144 |
145 | throw new Error('ERROR in authenticate: Name of current framework is not supported.');
146 | }
147 |
148 | /**
149 | * Takes in a name for a serializer function and the serializer function the
150 | * developer specifies. Serializer function needs to do 4 things below:
151 | *
152 | * 1. The serializer function needs to take in one parameter which will be the
153 | * user data in the form of an object.
154 | * 2. The serializer function needs to specify what the developer wants to do
155 | * with the user data (store it somewhere, add some info to response body,
156 | * etc).
157 | * 3. The serializer function should to specify how to create a serialized ID.
158 | * 4. The serializer function should return the serialized ID or an error.
159 | *
160 | * EXAMPLE
161 | *
162 | * dashport.addSerializer('1', (userInfo) => {
163 | * const serializedId = Math.random() * 10000;
164 | *
165 | * try {
166 | * // do something with userInfo like store in a database
167 | * return serializedId;
168 | * } catch(err) {
169 | * // err should be an instance of 'Error'
170 | * return err;
171 | * }
172 | * });
173 | *
174 | * @param {string} serializerName - A name to give the serializer if it needs
175 | * to be deleted later
176 | * @param {Function} serializer - The function that will create serialized IDs
177 | */
178 | public addSerializer(serializerName: string, serializer: Function): void {
179 | if (serializer.length !== 1) {
180 | throw new Error('ERROR in addSerializer: Serializer function must have 1 parameter that is the userInfo.');
181 | }
182 |
183 | // the below if statement is currently not needed. TODO in SessionManager.serialize method
184 | // if (serializerName === 'all') {
185 | // throw new Error('ERROR in addSerializer: Cannot use the name \'all\'. It is a reserved keyword Dashport uses.')
186 | // }
187 |
188 | if (this._serializers[serializerName] !== undefined) {
189 | throw new Error('ERROR in addSerializer: A serializer with this name already exists.');
190 | }
191 |
192 | this._serializers[serializerName] = serializer;
193 | }
194 |
195 | /**
196 | * Removes a serializer from the this._serializers object.
197 | *
198 | * EXAMPLE
199 | *
200 | * dashport.removeSerializer('1');
201 | *
202 | * @param {string} serializerName - The name of the serializer to remove
203 | */
204 | public removeSerializer(serializerName: string): void {
205 | if (this._serializers[serializerName] === undefined) {
206 | throw new Error('ERROR in removeSerializer: The specified serializer does not exist.');
207 | }
208 |
209 | delete this._serializers[serializerName];
210 | }
211 |
212 | /**
213 | * Takes in a framework and returns a function that will become dashport's
214 | * deserialize method. _deserializeDecider runs inside the constructor of a
215 | * new Dashport instance.
216 | *
217 | * dashport.deserialize will act as middleware and invoke the specified
218 | * deserializer(s) if the serialized ID on the session object matches the
219 | * serialized ID on the current instance of Dashport.
220 | *
221 | * EXAMPLE: Using Oak as server framework
222 | *
223 | * // Below code written in a dashport configuration file
224 | * dashport.addDeserializer('A', (serializedId) => {
225 | * // code to specify how to obtain user info from serialized ID
226 | * })
227 | *
228 | * // Below code written in a router file
229 | * router.get('/iWantUserInfo',
230 | * dashport.deserialize('A'),
231 | * (ctx: OakContext, next: any) => {
232 | * ctx.response.body = 'Data was deserialized in previous middleware';
233 | * }
234 | * )
235 | *
236 | * TODO: Add other frameworks
237 | *
238 | * TODO: Current deserialize method for Oak uses the first deserializer in
239 | * _deserializers. Extend code to take in an extra parameter (a name) that
240 | * specifies which deserializer to use.
241 | *
242 | * @param {string} framework - The server framework that will be used
243 | * @returns {*} The async function that will be dashport's deserialize method
244 | */
245 | private _deserializeDecider(framework: string) {
246 | const self: Dashport = this;
247 |
248 | if (framework === 'oak') {
249 | return async (ctx: OakContext, next: any) => {
250 | if (Object.values(self._deserializers).length === 0) {
251 | throw new Error('ERROR in deserialize: No deserializers.');
252 | }
253 |
254 | let userInfo: any;
255 |
256 | if (ctx.state._dashport.session === undefined) {
257 | userInfo = new Error('ERROR in deserialize: No session exists');
258 | } else if (self._sId === ctx.state._dashport.session) {
259 | // a deserializer should either return the user info in an object or
260 | // an Error
261 | userInfo = await Object.values(self._deserializers)[0](ctx.state._dashport.session);
262 | } else {
263 | userInfo = new Error('ERROR in deserialize: serializedId cannot be authenticated');
264 | }
265 |
266 | // store the userInfo or the error in ctx.locals for next middleware to
267 | // access
268 | ctx.locals = userInfo;
269 | return await next();
270 | }
271 | }
272 |
273 | throw new Error('ERROR in _deserializeDecider: Name of current framework is not supported.');
274 | }
275 |
276 | /**
277 | * Takes in a name for a deserializer function and the deserializer function
278 | * the developer specifies. Deserializer function needs to do 3 things below:
279 | *
280 | * 1. The deserializer function needs to take in one parameter which will be
281 | * the serialized ID.
282 | * 2. The deserializer function needs to specify what the developer wants to
283 | * do with the serialized ID to obtain user info (e.g. fetch the userData from
284 | * a database).
285 | * 3. The deserializer function needs to return the user info or an Error.
286 | *
287 | * EXAMPLE
288 | *
289 | * dashport.addDeserializer('A', (serializedId) => {
290 | * // handle getting userInfo from a serializedId here. e.g. taking the ID
291 | * // and querying a DB for the info. If userInfo comes back successfully,
292 | * // return it. Otherwise return an error
293 | * try {
294 | * const userInfo = await (look up serializedId in a db here);
295 | * return userInfo;
296 | * } catch(err) {
297 | * return err;
298 | * }
299 | * })
300 | *
301 | * @param {string} deserializerName - A name to give the deserializer if it
302 | * needs to be deleted later
303 | * @param {Function} deserializer - The function that will take a serialized ID
304 | * and return the user info in an object or an Error
305 | */
306 | public addDeserializer(deserializerName: string, deserializer: Function): void {
307 | if (deserializer.length !== 1) {
308 | throw new Error('ERROR in addDeserializer: Deserializer function must have 1 parameter that is the serializedId.');
309 | }
310 |
311 | // the below if statement is currently not needed. TODO in Dashport._deserializeDecider method
312 | // if (deserializerName === 'all') {
313 | // throw new Error('ERROR in addDeserializer: Cannot use the name \'all\'. It is a reserved keyword Dashport uses.')
314 | // }
315 |
316 | if (this._deserializers[deserializerName] !== undefined) {
317 | throw new Error('ERROR in addDeserializer: A deserializer with this name already exists.');
318 | }
319 |
320 | this._deserializers[deserializerName] = deserializer;
321 | }
322 |
323 | /**
324 | * Removes a deserializer from the this._deserializers object.
325 | *
326 | * EXAMPLE
327 | *
328 | * dashport.removeDeserializer('A');
329 | *
330 | * @param {string} deserializerName - The name of the serializer to remove
331 | */
332 | public removeDeserializer(deserializerName: string): void {
333 | if (this._deserializers[deserializerName] === undefined || this._deserializers[deserializerName] === null) {
334 | throw new Error('ERROR in removeDeserializer: The specified deserializer does not exist.');
335 | }
336 |
337 | delete this._deserializers[deserializerName];
338 | }
339 |
340 | /**
341 | * Adds an OAuth strategy that the developer would like to use.
342 | *
343 | * EXAMPLE
344 | *
345 | * dashport.addStrategy('google', new GoogleStrategy());
346 | *
347 | * @param {string} stratName - The name that will be used to reference this strategy
348 | * @param {Class} strategy - The imported OAuth strategy module to be used
349 | */
350 | public addStrategy(stratName: string, strategy: Strategy): void {
351 | if (stratName === undefined || strategy === undefined) {
352 | throw new Error('ERROR in addStrategy: A strategy name and a strategy must be provided.');
353 | }
354 |
355 | // ALL strategies made for Dashport MUST have a 'router' method that on
356 | // successful authentication, returns an authData object with a userInfo
357 | // property in the form of UserProfile
358 | if (typeof(strategy.router) !== 'function') {
359 | throw new Error('ERROR in addStrategy: This strategy does not have a \'router\' method.');
360 | }
361 |
362 | if (this._strategies[stratName] !== undefined) {
363 | throw new Error('ERROR in addStrategy: This strategy already exists.');
364 | }
365 |
366 | this._strategies[stratName] = strategy;
367 | }
368 |
369 | /**
370 | * Removes an OAuth strategy from the _strategies attribute.
371 | *
372 | * EXAMPLE
373 | *
374 | * dashport.removeStrategy('google');
375 | *
376 | * @param {string} stratName - The name of the strategy to be deleted
377 | */
378 | public removeStrategy(stratName: string): void {
379 | if (stratName === undefined) {
380 | throw new Error('ERROR in removeStrategy: A strategy name must be provided.');
381 | }
382 |
383 | if (this._strategies[stratName] === undefined) {
384 | throw new Error('ERROR in removeStrategy: The specified strategy does not exist.');
385 | }
386 |
387 | delete this._strategies[stratName];
388 | }
389 | }
390 |
391 | export default Dashport;
--------------------------------------------------------------------------------
/deprecated/v1.0.1/sessionManager.test.ts:
--------------------------------------------------------------------------------
1 | import { assertEquals, assertNotEquals } from '../../deps.ts';
2 | import SessionManager from './sessionManager.ts';
3 | import Dashport from './dashport.ts';
4 | import { OakContext, Translators, UserProfile } from "../types.ts";
5 |
6 | const oakTestSM = new SessionManager('oak');
7 |
8 | Deno.test({
9 | name: "A new SessionManager should have the properties logIn, logOut, and serialize",
10 | fn(): void {
11 | assertNotEquals(oakTestSM.logIn, undefined);
12 | assertNotEquals(oakTestSM.logOut, undefined);
13 | assertNotEquals(oakTestSM.serialize, undefined);
14 | }
15 | });
16 |
17 | Deno.test({
18 | name: "When invoked, logIn method should (1) change the value of session property on ctx.state._dashport to the serializedId passed in and " +
19 | "(2) change the _sId property on dashport to the serializedId passed in",
20 | fn(): void {
21 | const oakTestDp = new Dashport('oak');
22 | const fakeOakCtx: OakContext = {
23 | app: {},
24 | cookies: {},
25 | request: {},
26 | respond: {},
27 | response: {},
28 | socket: {},
29 | state: {
30 | _dashport: {
31 | session: ''
32 | }
33 | },
34 | assert: () => 1,
35 | send: () => 2,
36 | sendEvents: () => 3,
37 | throw: () => 4,
38 | upgrade: () => 5,
39 | params: {}
40 | }
41 |
42 | assertEquals(fakeOakCtx.state._dashport.session, '');
43 | assertEquals(oakTestDp._sId, '');
44 | oakTestSM.logIn(fakeOakCtx, oakTestDp, '1234567890');
45 | assertEquals(fakeOakCtx.state._dashport.session, '1234567890');
46 | assertEquals(oakTestDp._sId, '1234567890');
47 | }
48 | });
49 |
50 | Deno.test({
51 | name: "When invoked, logOut method should delete the session property on ctx.state._dashport",
52 | async fn(): Promise {
53 | const fakeOakCtx: OakContext = {
54 | app: {},
55 | cookies: {},
56 | request: {},
57 | respond: {},
58 | response: {},
59 | socket: {},
60 | state: {
61 | _dashport: {
62 | session: '123'
63 | }
64 | },
65 | assert: () => 1,
66 | send: () => 2,
67 | sendEvents: () => 3,
68 | throw: () => 4,
69 | upgrade: () => 5,
70 | params: {}
71 | }
72 | const fakeNext: any = () => 'fakeNext was invoked';
73 |
74 | assertEquals(fakeOakCtx.state._dashport.session, '123');
75 | await oakTestSM.logOut(fakeOakCtx, fakeNext);
76 | assertEquals(fakeOakCtx.state._dashport.session, undefined);
77 | }
78 | });
79 |
80 | Deno.test({
81 | name: "When invoked, serialize method should execute a serializer function from an object of serializers and return a serialized ID",
82 | fn(): void {
83 | const testProfile: UserProfile = {
84 | provider: 'google',
85 | providerUserId: '0987654321',
86 | displayName: 'Dashport',
87 | name: {
88 | familyName: 'port',
89 | givenName: 'Dash'
90 | }
91 | };
92 |
93 | const fakeDB: any = {};
94 |
95 | function testFunc(userInfo: UserProfile) {
96 | fakeDB['test'] = userInfo
97 | return '2468';
98 | };
99 |
100 | const testSerializers: Translators = {
101 | '1': testFunc
102 | };
103 |
104 | // fakeDB should be empty
105 | assertEquals(fakeDB, {});
106 | // running serialize should output a serialized ID ('2468')
107 | assertEquals(oakTestSM.serialize(testSerializers, testProfile), '2468');
108 | // after running serialize, fakeDB should be filled with the test info
109 | assertEquals(fakeDB, {
110 | test: {
111 | provider: 'google',
112 | providerUserId: '0987654321',
113 | displayName: 'Dashport',
114 | name: {
115 | familyName: 'port',
116 | givenName: 'Dash'
117 | }
118 | }
119 | });
120 | }
121 | });
122 |
--------------------------------------------------------------------------------
/deprecated/v1.0.1/sessionManager.ts:
--------------------------------------------------------------------------------
1 | import { OakContext, Translators, UserProfile } from './types.ts';
2 | import Dashport from './dashport.ts';
3 |
4 | /**
5 | * Takes in the framework to use for SessionManager
6 | *
7 | * EXAMPLE:
8 | *
9 | * const sm = new SessionManager('oak');
10 | *
11 | * When using an instance of SessionManager, only use
12 | * sm.logIn();
13 | * sm.logOut();
14 | * sm.serialize();
15 | *
16 | * @param {string} framework - The name of the server framework to be used
17 | */
18 | class SessionManager {
19 | public logIn: Function;
20 | public logOut: Function;
21 |
22 | constructor(framework: string) {
23 | this.logIn = this._logInDecider(framework);
24 | this.logOut = this._logOutDecider(framework);
25 | }
26 |
27 | /**
28 | * Takes in the name of the framework the developer is using and returns a
29 | * function that will add a session object onto the browser's http object.
30 | * This will allow information across different requests to persist.
31 | *
32 | * TODO: Add an optional parameter on the returned function that takes in an
33 | * expiration date for the session
34 | * TODO: Add other frameworks
35 | *
36 | * @param {string} framework - Name of the framework the developer is using
37 | * @returns {Function} The function that adds the dashport.session object onto
38 | * the browser's http object
39 | */
40 | private _logInDecider(framework: string): Function {
41 | if (framework = 'oak') {
42 | return function(ctx: OakContext, dashport: Dashport, serializedId: string): void {
43 | if (ctx.state._dashport) {
44 | ctx.state._dashport.session = serializedId;
45 | dashport._sId = serializedId;
46 | } else throw new Error('ERROR in _logInDecider: ctx.state._dashport does not exist. Please use app.use(dashport.initialize).')
47 | }
48 | }
49 |
50 | throw new Error('ERROR in _logInDecider: Name of framework passed in is not supported.');
51 | }
52 |
53 | /**
54 | * Takes in the name of the framework the developer is using and returns a
55 | * middleware that will delete the session object on the browser's http object
56 | *
57 | * TODO: Add other frameworks
58 | *
59 | * @param {string} framework - Name of the framework the developer is using
60 | * @returns {Function} The middleware that deletes the session object from the
61 | * browser's http object
62 | */
63 | private _logOutDecider(framework: string): Function {
64 | if (framework = 'oak') {
65 | return async (ctx: OakContext, next: any) => {
66 | delete ctx.state._dashport.session;
67 | await next();
68 | }
69 | }
70 |
71 | throw new Error('ERROR in _logOutDecider: Name of framework passed in is not supported.');
72 | }
73 |
74 | /**
75 | * Takes in an object of serializer functions and currently - read TODO - uses
76 | * the first one to create a serialized ID and return it.
77 | *
78 | * TODO: Allow a 'name' parameter to be passed in that specifies which
79 | * serializer to use
80 | * TODO: If name === 'all', use all the serializers in a chain.
81 | *
82 | * TODO: Allow optional parameters to be passed into the serializer to be
83 | * used. If chaining multiple serializers is implemented, pass params into the
84 | * first serializer function.
85 | *
86 | * @param {Object} serializers - An object containing serializer functions
87 | * @param {Object} userData - An object containing the authenticated user data
88 | * @returns {string} A serialized ID
89 | */
90 | public serialize(serializers: Translators, userData: UserProfile): string {
91 | if (Object.values(serializers).length === 0) {
92 | throw new Error('ERROR in serialize: No serializers.');
93 | }
94 |
95 | // Object.values(serializers)[0] returns the first key/value pair's
96 | // value. We are then invoking it (since it should be a function) and
97 | // returning what's returned (should be a serialized ID)
98 | return Object.values(serializers)[0](userData);
99 | }
100 | }
101 |
102 | export default SessionManager;
103 |
--------------------------------------------------------------------------------
/deps.ts:
--------------------------------------------------------------------------------
1 | export { Application } from "https://deno.land/x/oak/mod.ts"
2 |
3 | export {
4 | assertEquals,
5 | assertThrows,
6 | assertThrowsAsync,
7 | } from "https://deno.land/std@0.87.0/testing/asserts.ts";
8 |
--------------------------------------------------------------------------------
/mod.ts:
--------------------------------------------------------------------------------
1 | import DashportOak from './dashports/dashportOak.ts';
2 | export { DashportOak };
3 |
--------------------------------------------------------------------------------
/templateStrategy.ts:
--------------------------------------------------------------------------------
1 | import { OakContext, StrategyOptions, AuthData } from '../types.ts';
2 |
3 | /**
4 | * Creates an instance of `TemplateStrategy`.
5 | *
6 | *
7 | * * Options:
8 | *
9 | * - client_id: string Required
10 | * - client_secret: string Required
11 | * - redirect_uri: string Required
12 | *
13 | */
14 | export default class TemplateStrategy {
15 | name: string = '';
16 | options: StrategyOptions;
17 | uriFromParams: string;
18 | // ACTION NEEDED:
19 | // add the url for the first endpoint here
20 | authURL: string = ''
21 | // add the url to exchange the auth code for a token here
22 | tokenURL: string = ''
23 | // add the url to exchange the token for auth data here
24 | authDataURL: string = ''
25 | /**
26 | * @constructor
27 | * @param {Object} options
28 | * @api public
29 | */
30 | constructor (options: StrategyOptions) {
31 | // customize with whatever fields are required to send to first redirect
32 | if (!options.client_id || !options.redirect_uri || !options.client_secret) {
33 | throw new Error('ERROR in TemplateStrategy constructor: Missing required arguments');
34 | }
35 |
36 | this.options = options;
37 |
38 | // PRE STEP 1:
39 | // Constructs the second half of the authURL for developer's first endpoint from the info put into 'options'
40 | // ACTION NEEDED:
41 | // If there are any variables in options that aren't needed for developer's first endpoint (but will be needed later),
42 | // add them as an array of strings (even if there's only 1 item)
43 | this.uriFromParams = this.constructURI(this.options);
44 | }
45 |
46 | constructURI(options: any, skip?: string[]): any {
47 | const paramArray: string[][] = Object.entries(options);
48 | let paramString: string = '';
49 |
50 | for (let i = 0; i < paramArray.length; i++) {
51 | let [key, value] = paramArray[i];
52 |
53 | if (skip && skip.includes(key)) continue;
54 | // adds the key and '=' for every member of options not in the skip array
55 | paramString += (key + '=');
56 | // adds the value and '&' for every member of options not in the skip array
57 | paramString += (value + '&');
58 | }
59 |
60 | // removes the '&' that was just placed at the end of the string
61 | if (paramString[paramString.length - 1] === '&') {
62 | paramString = paramString.slice(0, -1);
63 | }
64 |
65 | return paramString;
66 | }
67 |
68 | // parses an encoded URI
69 | parseCode(encodedCode: string): string {
70 | const replacements: { [name: string] : string } = {
71 | "%24": "$",
72 | "%26": "&",
73 | "%2B": "+",
74 | "%2C": ",",
75 | "%2F": "/",
76 | "%3A": ":",
77 | "%3B": ";",
78 | "%3D": "=",
79 | "%3F": "?",
80 | "%40": "@"
81 | }
82 | const toReplaceArray: string[] = Object.keys(replacements);
83 |
84 | for (let i = 0; i < toReplaceArray.length; i++) {
85 | while (encodedCode.includes(toReplaceArray[i])) {
86 | encodedCode = encodedCode.replace(toReplaceArray[i], replacements[toReplaceArray[i]]);
87 | }
88 | }
89 |
90 | return encodedCode;
91 | }
92 |
93 | // ENTRY POINT
94 | async router(ctx: OakContext, next: any) {
95 | // GO_Step 1 Request Permission
96 | if (!ctx.request.url.search) return await this.authorize(ctx, next);
97 | // GO_Step 3 Exchange code for Token
98 | // ACTION REQUIRED: verify that a successful response from getAuthToken includes 'code' in the location specified below
99 | if (ctx.request.url.search.slice(1, 5) === 'code') return this.getAuthToken(ctx, next);
100 | }
101 |
102 | // STEP 1: sends the programatically constructed uri to an OAuth 2.0 server
103 | async authorize(ctx: OakContext, next: any) {
104 | return await ctx.response.redirect(this.authURL + this.uriFromParams);
105 | }
106 |
107 | // STEP 2: client says yes or no
108 |
109 | // STEP 3: handle OAuth 2.0 server response containing auth code
110 | // STEP 3.5: request access token in exchange for auth code
111 | async getAuthToken(ctx: OakContext, next: any) {
112 | // the URI sent back to the endpoint provided in step 1
113 | const OGURI: string = ctx.request.url.search;
114 |
115 | if (OGURI.includes('error')) {
116 | return new Error('ERROR in getAuthToken: Received an error from auth token code request.');
117 | }
118 |
119 | // EXTRACT THE AUTH CODE
120 | // ACTION REQUIRED: verify that this function works for the format of the response received. uncomment the line below to test:
121 | // console.log('AUTH RESPONSE:', OGURI);
122 | // splits the string at the '=,' storing the first part in URI1[0] and the part wanted in URI1[1]
123 | let URI1: string[] = OGURI.split('=');
124 | // splits the string at the '&', storing the string with the access_token in URI2[0]
125 | // and the other parameters at URI2[n]
126 | const URI2: string[] = URI1[1].split('&');
127 | // PARSE THE URI
128 | const code: string = this.parseCode(URI2[0]);
129 |
130 | // STEP 3.5
131 | // ACTION REQUIRED: add or remove the parameters needed to send as response to token request
132 | const tokenOptions: any = {
133 | client_id: this.options.client_id,
134 | redirect_uri: this.options.redirect_uri,
135 | client_secret: this.options.client_secret,
136 | code: code,
137 | }
138 |
139 | // SEND A FETCH REQ FOR TOKEN
140 | try {
141 | let data: any = await fetch(this.tokenURL + this.constructURI(tokenOptions));
142 | data = await data.json();
143 |
144 | if (data.type === 'oAuthException') {
145 | return new Error('ERROR in getAuthToken: Token request threw OAuth exception.');
146 | }
147 |
148 | // PASSES TOKEN ON TO STEP 4
149 | return this.getAuthData(data);
150 | } catch(err) {
151 | return new Error(`ERROR in getAuthToken: Unable to obtain token - ${err}`);
152 | }
153 | }
154 |
155 | // STEP 4 get the access token from the returned data
156 | // STEP 4.5 exchange access token for user info
157 | async getAuthData(parsed: any){
158 | // ACTION REQUIRED:
159 | // fill in the fields for tokenData based on the token obj obtained in the last step
160 | // authData is what is going to be passed back to dashport's authenticate method
161 | const authData: AuthData = {
162 | tokenData: {
163 | access_token: parsed.access_token,
164 | token_type: parsed.token_type,
165 | expires_in: parsed.expires_in,
166 | },
167 | userInfo: {
168 | provider: '',
169 | providerUserId: ''
170 | }
171 | }
172 |
173 | // STEP 4.5: request user info
174 | // ACTION REQUIRED:
175 | // fill in the fields for auth options with the information required by requested OAuth service
176 | // authOptions constructs the uri for the final fetch request
177 | const authOptions: any = {
178 | input_token: authData.tokenData.access_token,
179 | access_token: this.options.client_id + '|' + this.options.client_secret
180 | };
181 |
182 | try {
183 | let data: any = await fetch(this.authDataURL + this.constructURI(authOptions));
184 | data = await data.json();
185 |
186 | // ACTION REQUIRED:
187 | // Add any data to pass back to dashport's authenticate method here
188 | authData.userInfo = {
189 | provider: this.name,
190 | providerUserId: data.data.user_id
191 | };
192 |
193 | return authData;
194 | } catch(err) {
195 | return new Error(`ERROR in getAuthData: Unable to obtain auth data - ${err}`);
196 | }
197 | }
198 | }
199 |
--------------------------------------------------------------------------------
/types.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Should contain the same properties and methods defined by Oak
3 | * https://github.com/oakserver/oak
4 | */
5 | export interface OakContext {
6 | app: any;
7 | cookies: any;
8 | request: any;
9 | respond: any;
10 | response: any;
11 | socket: any;
12 | state: any;
13 | assert: Function;
14 | send: Function;
15 | sendEvents: Function;
16 | throw: Function;
17 | upgrade: Function;
18 | params?: any;
19 | locals?: any;
20 | }
21 |
22 | /**
23 | * All Dashport strategies are classes that must contain a router method
24 | */
25 | export interface Strategy {
26 | router: Function;
27 | }
28 |
29 | /**
30 | * Different OAuths will return different user information in different
31 | * structures. Dashport strategies should break down and reconstruct the user
32 | * info into the standardized UserProfile below
33 | */
34 | export interface UserProfile {
35 | // the provider the user is authenticated with
36 | provider: string;
37 | // the unique id a user has with that specific provider
38 | providerUserId: string;
39 | // the display name or username for this specific user
40 | displayName?: string;
41 | name?: {
42 | familyName?: string;
43 | givenName?: string;
44 | middleName?: string;
45 | };
46 | emails?: Array;
47 | }
48 |
49 | /**
50 | * All OAuth 2.0 providers will provide access tokens
51 | */
52 | export interface TokenData {
53 | access_token: string;
54 | [options: string]: string;
55 | }
56 |
57 | /**
58 | * The form the information from strategies should come back in
59 | */
60 | export interface AuthData {
61 | tokenData: TokenData;
62 | userInfo: UserProfile;
63 | }
64 |
65 | /**
66 | * At the bare minimum, OAuth 2.0 providers will require a client ID, client
67 | * secret, and redirect URI. The remaining options depend on the OAuth 2.0
68 | * provider, such as scope or state
69 | */
70 | export interface StrategyOptions {
71 | client_id: string;
72 | client_secret: string;
73 | redirect_uri: string;
74 | [option: string]: string;
75 | }
76 |
--------------------------------------------------------------------------------