@ministryofjustice/hmpps-prisoner-auth 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # Prisoner Auth
2
+
3
+ ## Installation
4
+
5
+ ### Quick installation
6
+
7
+ The following steps will give an opinionated out of the box setup for applications where a launchpad user will be the main user in the system.
8
+
9
+ 1. Add the package `@ministryofjustice/hmpps-prisoner-auth` to your `package.json`
10
+ ```
11
+ $ npm add --save @ministryofjustice/hmpps-prisoner-auth
12
+ $ npm i
13
+ ```
14
+ 2. Copy the contents of [src/misc/install.diff](src/misc/install.diff) to your local folder
15
+ ```
16
+ $ curl https://raw.githubusercontent.com/ministryofjustice/hmpps-prisoner-facing-typescript-lib/refs/heads/main/packages/prisoner-auth/src/misc/install.diff > install.diff
17
+ ```
18
+ 3. Apply the diff to carry out all the required changes
19
+ ```
20
+ $ git apply install.diff
21
+ ```
22
+ 4. Make sure to look through the applied changes and remove the diff file before committing.
23
+
24
+ ### Manual installation
25
+
26
+ 1. Add the package `@ministryofjustice/hmpps-prisoner-auth` to your `package.json`
27
+ ```
28
+ $ npm add --save @ministryofjustice/hmpps-prisoner-auth
29
+ $ npm i
30
+ ```
31
+ 2. Modify `interfaces/hmppsUser.ts` to make the following additions:
32
+ ```
33
+ # add prisoner-auth to the AuthSource type
34
+ export type AuthSource = 'nomis' | 'delius' | 'external' | 'azuread' | 'prisoner-auth'
35
+
36
+ # add LaunchpadUser to the HmppsUser type
37
+ export type HmppsUser = PrisonUser | ProbationUser | ExternalUser | AzureADUser | LaunchpadUser
38
+ ```
39
+ These changes will help smooth over alot of assumptions made in the typescript template about the type of fields the user will have.
40
+
41
+ 3. Modify `server/middleware/setupAuthentication.ts` to add prisoner auth as the passport strategy (see below for the full list of config options):
42
+ ```
43
+ passport.use(
44
+ 'prisoner-auth',
45
+ prisonerAuthStrategy(
46
+ {
47
+ launchpadAuthUrl: 'https://launchpad.instance.etc' ...
48
+ }
49
+ )
50
+ )
51
+ ```
52
+ Note: it is recommended to give the strategy a name ('prisoner-auth' here) for later referral with passport.authenticate middleware.
53
+
54
+ 4. Modify `server/middleware/setupAuthentication.ts` to remove any hmpps auth specific code and add in prisoner auth setup. The following code snippet is intended as a guide:
55
+ ```
56
+ export default function setupAuthentication() {
57
+ const router = Router()
58
+
59
+ router.use(passport.initialize())
60
+ router.use(passport.session())
61
+ router.use(flash())
62
+
63
+ router.get('/autherror', (req, res) => {
64
+ res.status(401)
65
+ return res.render('autherror')
66
+ })
67
+
68
+ router.get('/sign-in', passport.authenticate('prisoner-auth'))
69
+
70
+ router.get('/sign-in/callback', (req, res, next) =>
71
+ passport.authenticate('prisoner-auth', {
72
+ successReturnToOrRedirect: req.session.returnTo || '/',
73
+ failureRedirect: '/autherror',
74
+ })(req, res, next),
75
+ )
76
+
77
+ router.use('/sign-out', (req, res, next) => {
78
+ if (req.user) {
79
+ req.logout(err => {
80
+ if (err) return next(err)
81
+ return req.session.destroy(() => res.redirect('/'))
82
+ })
83
+ } else res.redirect('/')
84
+ })
85
+
86
+ router.use(async (req, res, next) => {
87
+ if (!req.isAuthenticated()) {
88
+ req.session.returnTo = req.originalUrl
89
+ return res.redirect('/sign-in')
90
+ }
91
+
92
+ return prisonerAuth
93
+ .validateAndRefreshUser(req.user as LaunchpadUser)
94
+ .then(user => {
95
+ req.user = user
96
+ next()
97
+ })
98
+ .catch(() => res.redirect('/autherror'))
99
+ })
100
+
101
+ router.use((req, res, next) => {
102
+ res.locals.user = req.user as HmppsUser
103
+ next()
104
+ })
105
+
106
+ return router
107
+ }
108
+ ```
109
+ 5. You may wish to remove the following middleware from `server/app.ts` as they were not designed for use with `LaunchpadUser`:
110
+ - `authorisationMiddleware()` - this checks the users `ROLE_` which a launchpad user will not have.
111
+ - `setUpCurrentUser()` - this sets up the `res.locals.user` fields which a launchpad will have already (`name`, `displayName` etc).
112
+
113
+ ### Configuration Options
114
+
115
+ | Option | Description |
116
+ | - | - |
117
+ | launchpadAuthUrl | The full URL of the launchpad auth instance you wish to authenticate against |
118
+ | clientID | The client ID |
119
+ | clientSecret | The client secret |
120
+ | callbackURL | *Optional, The URL or path you wish launchpad to call back to in the auth flow. Defaults to `/signin/callback` |
121
+ | scope | *Optional, One or more OpenIdConnect Scopes you require for the application. Defaults to `['user.basic.read', 'user.establishment.read', 'user.booking.read']` |
122
+ | tokenMinimumLifespan | The least amount of time a token must have left before it is considered expired. Expressed as a TimeSpan. eg. `minutes(5)` or `nothing()`. |
123
+
124
+ See `PrisonerAuthOptions` for full list and documentation for these options.
@@ -0,0 +1,182 @@
1
+ 'use strict';
2
+
3
+ var OpenIDConnectStrategy = require('passport-openidconnect');
4
+ var superagent = require('superagent');
5
+
6
+ const tokenFromJwt = (token) => JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
7
+ const oauthClientToken = (clientId, clientSecret) => {
8
+ const token = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
9
+ return `Basic ${token}`;
10
+ };
11
+ const tokenFetcher = (userRefreshToken, tokenUrl, authorizationHeader) => {
12
+ return new Promise((resolve, reject) => {
13
+ superagent
14
+ .post(tokenUrl)
15
+ .set('Content-Type', 'application/x-www-form-urlencoded')
16
+ .set('Authorization', authorizationHeader)
17
+ .query({ refresh_token: userRefreshToken, grant_type: 'refresh_token' })
18
+ .end((err, res) => {
19
+ if (err)
20
+ reject(err);
21
+ const { id_token: idToken, access_token: accessToken, refresh_token: refreshToken } = res.body;
22
+ return resolve({ idToken, accessToken, refreshToken });
23
+ });
24
+ });
25
+ };
26
+
27
+ const userFromTokens = ({ idToken, accessToken, refreshToken }) => {
28
+ const { establishment, name, given_name: givenName, family_name: familyName, sub } = tokenFromJwt(idToken);
29
+ const authSource = 'prisoner-auth';
30
+ return {
31
+ authSource,
32
+ idToken,
33
+ refreshToken,
34
+ accessToken,
35
+ establishment,
36
+ name,
37
+ givenName,
38
+ familyName,
39
+ // The following fields are for compatibility with HmppsUser only
40
+ token: idToken,
41
+ username: name,
42
+ userId: sub,
43
+ displayName: name,
44
+ userRoles: [],
45
+ };
46
+ };
47
+
48
+ // A TimeSpan allows you to give meaning to a number and then read that number back and compare them in different formats
49
+ // ie.
50
+ // seconds(60).minutes === 1
51
+ // seconds(30).seconds === 30
52
+ // minutes(1).seconds === 60
53
+ // milliseconds(1000).seconds === 1
54
+ // milliseconds(2000).isEqualTo(seconds(2)) #=> true
55
+ // minutes(5).
56
+ // This allows a caller to just accept a TimeSpan and then work in the units it cares about
57
+ const minutes = (numMinutes) => TimeSpan.minutes(numMinutes);
58
+ const seconds = (numSeconds) => TimeSpan.seconds(numSeconds);
59
+ const milliseconds = (numMilliseconds) => TimeSpan.milliseconds(numMilliseconds);
60
+ const nothing = () => TimeSpan.nothing();
61
+ // Get a time span in the future
62
+ const timeFromNow = (fromNow) => milliseconds(Date.now() + fromNow.milliseconds);
63
+ // Implementation
64
+ class TimeSpan {
65
+ minutes;
66
+ seconds;
67
+ milliseconds;
68
+ constructor(numMinutes, numSeconds, numMilliseconds) {
69
+ this.minutes = numMinutes;
70
+ this.seconds = numSeconds;
71
+ this.milliseconds = numMilliseconds;
72
+ }
73
+ static minutes(numMinutes) {
74
+ return new TimeSpan(numMinutes, numMinutes * 60, TimeSpan.seconds(numMinutes * 60).milliseconds);
75
+ }
76
+ static seconds(numSeconds) {
77
+ return new TimeSpan(numSeconds / 60, numSeconds, numSeconds * 1000);
78
+ }
79
+ static milliseconds(numMilliseconds) {
80
+ return new TimeSpan(TimeSpan.seconds(numMilliseconds / 1000).minutes, numMilliseconds / 1000, numMilliseconds);
81
+ }
82
+ static nothing() {
83
+ return new TimeSpan(0, 0, 0);
84
+ }
85
+ isGreaterThan(t) {
86
+ return this.milliseconds > t.milliseconds;
87
+ }
88
+ isGreaterThanOrEqualTo(t) {
89
+ return this.milliseconds >= t.milliseconds;
90
+ }
91
+ isLessThan(t) {
92
+ return this.milliseconds < t.milliseconds;
93
+ }
94
+ isLessThanOrEqualTo(t) {
95
+ return this.milliseconds <= t.milliseconds;
96
+ }
97
+ isEqualTo(t) {
98
+ return this.milliseconds === t.milliseconds;
99
+ }
100
+ }
101
+
102
+ class PrisonerAuth {
103
+ launchpadAuthUrl;
104
+ authorizationUrl;
105
+ tokenUrl;
106
+ callbackUrl;
107
+ clientId;
108
+ clientSecret;
109
+ scope;
110
+ nonce;
111
+ tokenMinimumLifespan;
112
+ tokenFetcher;
113
+ constructor(options) {
114
+ this.launchpadAuthUrl = options.launchpadAuthUrl;
115
+ this.authorizationUrl = options.authorizationUrl ?? `${options.launchpadAuthUrl}/v1/oauth2/authorize`;
116
+ this.tokenUrl = options.tokenUrl ?? `${options.launchpadAuthUrl}/v1/oauth2/token`;
117
+ this.callbackUrl = options.callbackUrl ?? '/sign-in/callback';
118
+ this.clientId = options.clientId;
119
+ this.clientSecret = options.clientSecret;
120
+ this.scope = options.scope ?? ['user.basic.read', 'user.establishment.read', 'user.booking.read'];
121
+ this.nonce = options.nonce ?? true;
122
+ this.tokenMinimumLifespan = options.tokenMinimumLifespan;
123
+ this.tokenFetcher = options.tokenFetcher ?? tokenFetcher;
124
+ }
125
+ /**
126
+ *
127
+ * @returns OpenIDConnectStrategy ready for use with passport
128
+ */
129
+ passportStrategy() {
130
+ return new OpenIDConnectStrategy({
131
+ issuer: this.launchpadAuthUrl,
132
+ authorizationURL: this.authorizationUrl,
133
+ tokenURL: this.tokenUrl,
134
+ callbackURL: this.callbackUrl,
135
+ clientID: this.clientId,
136
+ clientSecret: this.clientSecret,
137
+ scope: this.scope,
138
+ userInfoURL: '',
139
+ skipUserProfile: true,
140
+ nonce: this.nonce ? 'true' : undefined,
141
+ customHeaders: { authorization: this.authorizationToken() },
142
+ }, ((_issuer, _profile, _context, idToken, accessToken, refreshToken, done) => done(null, userFromTokens({ idToken, accessToken, refreshToken }))));
143
+ }
144
+ /**
145
+ * Checks the expiry of a given LaunchpadUser idToken and refreshToken.
146
+ * - If the idToken has expired it will attempt to refresh it using the refreshToken.
147
+ * - If the refreshToken has expired it will throw an Error.
148
+ *
149
+ * This method will always return a LaunchpadUser so it is suggested to updated your local copy and it will keep the tokens up to date.
150
+ *
151
+ * @param user - The LaunchpadUser instance to check token expiry on.
152
+ * @returns A LaunchpadUser is always returned, either the given user or a user with refreshed tokens.
153
+ * @throws Error is thrown if the refreshToken has expired, the user must sign in again from scratch.
154
+ */
155
+ async validateAndRefreshUser(user) {
156
+ const idToken = tokenFromJwt(user.idToken);
157
+ const refreshToken = tokenFromJwt(user.refreshToken);
158
+ const tokenExpiryTime = timeFromNow(this.tokenMinimumLifespan);
159
+ // Id Token still valid, return the current user
160
+ if (seconds(idToken.exp).isGreaterThanOrEqualTo(tokenExpiryTime)) {
161
+ return user;
162
+ }
163
+ // Refresh token expired, cannot continue
164
+ if (seconds(refreshToken.exp).isLessThan(tokenExpiryTime)) {
165
+ throw new Error('Refresh token expired');
166
+ }
167
+ // Refresh the user tokens
168
+ const newTokens = await this.tokenFetcher(user.refreshToken, this.tokenUrl, this.authorizationToken());
169
+ return userFromTokens(newTokens);
170
+ }
171
+ authorizationToken() {
172
+ return oauthClientToken(this.clientId, this.clientSecret);
173
+ }
174
+ }
175
+
176
+ exports.PrisonerAuth = PrisonerAuth;
177
+ exports.TimeSpan = TimeSpan;
178
+ exports.milliseconds = milliseconds;
179
+ exports.minutes = minutes;
180
+ exports.nothing = nothing;
181
+ exports.seconds = seconds;
182
+ //# sourceMappingURL=index.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/main/tokens.ts","../src/main/launchpadUser.ts","../src/main/timeSpans.ts","../src/main/prisonerAuth.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":"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"}
@@ -0,0 +1,162 @@
1
+ import OpenIDConnectStrategy from 'passport-openidconnect';
2
+
3
+ type IdToken = {
4
+ name: string;
5
+ given_name: string;
6
+ family_name: string;
7
+ nonce?: string;
8
+ iat: number;
9
+ aud: string;
10
+ sub: string;
11
+ exp: number;
12
+ booking: {
13
+ id: string;
14
+ };
15
+ establishment: {
16
+ agency_id: string;
17
+ name: string;
18
+ display_name: string;
19
+ youth: boolean;
20
+ };
21
+ iss: string;
22
+ };
23
+ type RawTokens = {
24
+ idToken: string;
25
+ refreshToken: string;
26
+ accessToken: string;
27
+ };
28
+ type Scope = 'user.basic.read' | 'user.establishment.read' | 'user.booking.read' | 'user.clients.read' | 'user.clients.delete';
29
+ type TokenFetcher = (refreshToken: string, tokenUrl: string, authorizationHeader: string) => Promise<RawTokens>;
30
+
31
+ type LaunchpadUser = {
32
+ authSource: 'prisoner-auth';
33
+ idToken: string;
34
+ refreshToken: string;
35
+ accessToken: string;
36
+ establishment: IdToken['establishment'];
37
+ name: string;
38
+ givenName: string;
39
+ familyName: string;
40
+ token: string;
41
+ username: string;
42
+ userId: string;
43
+ displayName: string;
44
+ userRoles: string[];
45
+ };
46
+
47
+ declare const minutes: (numMinutes: number) => TimeSpan;
48
+ declare const seconds: (numSeconds: number) => TimeSpan;
49
+ declare const milliseconds: (numMilliseconds: number) => TimeSpan;
50
+ declare const nothing: () => TimeSpan;
51
+ declare class TimeSpan {
52
+ readonly minutes: number;
53
+ readonly seconds: number;
54
+ readonly milliseconds: number;
55
+ private constructor();
56
+ static minutes(numMinutes: number): TimeSpan;
57
+ static seconds(numSeconds: number): TimeSpan;
58
+ static milliseconds(numMilliseconds: number): TimeSpan;
59
+ static nothing(): TimeSpan;
60
+ isGreaterThan(t: TimeSpan): boolean;
61
+ isGreaterThanOrEqualTo(t: TimeSpan): boolean;
62
+ isLessThan(t: TimeSpan): boolean;
63
+ isLessThanOrEqualTo(t: TimeSpan): boolean;
64
+ isEqualTo(t: TimeSpan): boolean;
65
+ }
66
+
67
+ type PrisonerAuthOptions = {
68
+ /** The full URL of the Launchpad Auth instance */
69
+ launchpadAuthUrl: string;
70
+ /**
71
+ * Optional: The full URL of the authorisation path on the Launchpad Auth instance.
72
+ *
73
+ * If omitted, authorizationUrl will default to $launchpadAuthUrl/v1/oauth2/authorize
74
+ */
75
+ authorizationUrl?: string;
76
+ /**
77
+ * Optional: The full URL of the token path on the Launchpad Auth instance.
78
+ *
79
+ * If omitted, tokenUrl will default to $launchpadAuthUrl/v1/oauth2/token
80
+ */
81
+ tokenUrl?: string;
82
+ /**
83
+ * Optional: The URL or path that Launchpad Auth will be told to call back to with the tokens.
84
+ *
85
+ * If omitted, callbackUrl will default to /sign-in/callback
86
+ */
87
+ callbackUrl?: string;
88
+ /** The Client ID of this application to send to Launchpad Auth */
89
+ clientId: string;
90
+ /** The Client Secret of this application to send to Launchpad Auth */
91
+ clientSecret: string;
92
+ /**
93
+ * Optional: The scope or scopes to request on behalf of the user.
94
+ *
95
+ * If omitted, scope will default to user.basic.read, user.establishment.read and user.booking.read
96
+ */
97
+ scope?: Scope | Scope[];
98
+ /**
99
+ * Optional: Whether or not to use nonce, not recommended to be disabled except for integration tests
100
+ *
101
+ * If ommitted. defaults to true
102
+ */
103
+ nonce?: boolean;
104
+ /**
105
+ * A time span specifying the least amount of time a token must have left before it is considered expired.
106
+ *
107
+ * @example
108
+ *
109
+ * Token will be considered expired when its exp value is in the past.
110
+ * ```ts
111
+ * nothing()
112
+ * ```
113
+ *
114
+ * Token will be considered expired unless its exp value is **AT LEAST 5** minutes in the future from time of checking.
115
+ * ```ts
116
+ * minutes(5)
117
+ * ```
118
+ *
119
+ * @see TimeSpan
120
+ */
121
+ tokenMinimumLifespan: TimeSpan;
122
+ /**
123
+ * Optional: Token fetcher implementation. Intended for unit testing PrisonerAuth
124
+ *
125
+ * @see TokenFetcher
126
+ */
127
+ tokenFetcher?: TokenFetcher;
128
+ };
129
+ declare class PrisonerAuth {
130
+ readonly launchpadAuthUrl: string;
131
+ readonly authorizationUrl: string;
132
+ readonly tokenUrl: string;
133
+ readonly callbackUrl: string;
134
+ readonly clientId: string;
135
+ readonly clientSecret: string;
136
+ readonly scope: Scope | Scope[];
137
+ readonly nonce: boolean;
138
+ readonly tokenMinimumLifespan: TimeSpan;
139
+ readonly tokenFetcher: TokenFetcher;
140
+ constructor(options: PrisonerAuthOptions);
141
+ /**
142
+ *
143
+ * @returns OpenIDConnectStrategy ready for use with passport
144
+ */
145
+ passportStrategy(): OpenIDConnectStrategy;
146
+ /**
147
+ * Checks the expiry of a given LaunchpadUser idToken and refreshToken.
148
+ * - If the idToken has expired it will attempt to refresh it using the refreshToken.
149
+ * - If the refreshToken has expired it will throw an Error.
150
+ *
151
+ * This method will always return a LaunchpadUser so it is suggested to updated your local copy and it will keep the tokens up to date.
152
+ *
153
+ * @param user - The LaunchpadUser instance to check token expiry on.
154
+ * @returns A LaunchpadUser is always returned, either the given user or a user with refreshed tokens.
155
+ * @throws Error is thrown if the refreshToken has expired, the user must sign in again from scratch.
156
+ */
157
+ validateAndRefreshUser(user: LaunchpadUser): Promise<LaunchpadUser>;
158
+ private authorizationToken;
159
+ }
160
+
161
+ export { PrisonerAuth, TimeSpan, milliseconds, minutes, nothing, seconds };
162
+ export type { LaunchpadUser, PrisonerAuthOptions };
@@ -0,0 +1,175 @@
1
+ import OpenIDConnectStrategy from 'passport-openidconnect';
2
+ import superagent from 'superagent';
3
+
4
+ const tokenFromJwt = (token) => JSON.parse(Buffer.from(token.split('.')[1], 'base64').toString());
5
+ const oauthClientToken = (clientId, clientSecret) => {
6
+ const token = Buffer.from(`${clientId}:${clientSecret}`).toString('base64');
7
+ return `Basic ${token}`;
8
+ };
9
+ const tokenFetcher = (userRefreshToken, tokenUrl, authorizationHeader) => {
10
+ return new Promise((resolve, reject) => {
11
+ superagent
12
+ .post(tokenUrl)
13
+ .set('Content-Type', 'application/x-www-form-urlencoded')
14
+ .set('Authorization', authorizationHeader)
15
+ .query({ refresh_token: userRefreshToken, grant_type: 'refresh_token' })
16
+ .end((err, res) => {
17
+ if (err)
18
+ reject(err);
19
+ const { id_token: idToken, access_token: accessToken, refresh_token: refreshToken } = res.body;
20
+ return resolve({ idToken, accessToken, refreshToken });
21
+ });
22
+ });
23
+ };
24
+
25
+ const userFromTokens = ({ idToken, accessToken, refreshToken }) => {
26
+ const { establishment, name, given_name: givenName, family_name: familyName, sub } = tokenFromJwt(idToken);
27
+ const authSource = 'prisoner-auth';
28
+ return {
29
+ authSource,
30
+ idToken,
31
+ refreshToken,
32
+ accessToken,
33
+ establishment,
34
+ name,
35
+ givenName,
36
+ familyName,
37
+ // The following fields are for compatibility with HmppsUser only
38
+ token: idToken,
39
+ username: name,
40
+ userId: sub,
41
+ displayName: name,
42
+ userRoles: [],
43
+ };
44
+ };
45
+
46
+ // A TimeSpan allows you to give meaning to a number and then read that number back and compare them in different formats
47
+ // ie.
48
+ // seconds(60).minutes === 1
49
+ // seconds(30).seconds === 30
50
+ // minutes(1).seconds === 60
51
+ // milliseconds(1000).seconds === 1
52
+ // milliseconds(2000).isEqualTo(seconds(2)) #=> true
53
+ // minutes(5).
54
+ // This allows a caller to just accept a TimeSpan and then work in the units it cares about
55
+ const minutes = (numMinutes) => TimeSpan.minutes(numMinutes);
56
+ const seconds = (numSeconds) => TimeSpan.seconds(numSeconds);
57
+ const milliseconds = (numMilliseconds) => TimeSpan.milliseconds(numMilliseconds);
58
+ const nothing = () => TimeSpan.nothing();
59
+ // Get a time span in the future
60
+ const timeFromNow = (fromNow) => milliseconds(Date.now() + fromNow.milliseconds);
61
+ // Implementation
62
+ class TimeSpan {
63
+ minutes;
64
+ seconds;
65
+ milliseconds;
66
+ constructor(numMinutes, numSeconds, numMilliseconds) {
67
+ this.minutes = numMinutes;
68
+ this.seconds = numSeconds;
69
+ this.milliseconds = numMilliseconds;
70
+ }
71
+ static minutes(numMinutes) {
72
+ return new TimeSpan(numMinutes, numMinutes * 60, TimeSpan.seconds(numMinutes * 60).milliseconds);
73
+ }
74
+ static seconds(numSeconds) {
75
+ return new TimeSpan(numSeconds / 60, numSeconds, numSeconds * 1000);
76
+ }
77
+ static milliseconds(numMilliseconds) {
78
+ return new TimeSpan(TimeSpan.seconds(numMilliseconds / 1000).minutes, numMilliseconds / 1000, numMilliseconds);
79
+ }
80
+ static nothing() {
81
+ return new TimeSpan(0, 0, 0);
82
+ }
83
+ isGreaterThan(t) {
84
+ return this.milliseconds > t.milliseconds;
85
+ }
86
+ isGreaterThanOrEqualTo(t) {
87
+ return this.milliseconds >= t.milliseconds;
88
+ }
89
+ isLessThan(t) {
90
+ return this.milliseconds < t.milliseconds;
91
+ }
92
+ isLessThanOrEqualTo(t) {
93
+ return this.milliseconds <= t.milliseconds;
94
+ }
95
+ isEqualTo(t) {
96
+ return this.milliseconds === t.milliseconds;
97
+ }
98
+ }
99
+
100
+ class PrisonerAuth {
101
+ launchpadAuthUrl;
102
+ authorizationUrl;
103
+ tokenUrl;
104
+ callbackUrl;
105
+ clientId;
106
+ clientSecret;
107
+ scope;
108
+ nonce;
109
+ tokenMinimumLifespan;
110
+ tokenFetcher;
111
+ constructor(options) {
112
+ this.launchpadAuthUrl = options.launchpadAuthUrl;
113
+ this.authorizationUrl = options.authorizationUrl ?? `${options.launchpadAuthUrl}/v1/oauth2/authorize`;
114
+ this.tokenUrl = options.tokenUrl ?? `${options.launchpadAuthUrl}/v1/oauth2/token`;
115
+ this.callbackUrl = options.callbackUrl ?? '/sign-in/callback';
116
+ this.clientId = options.clientId;
117
+ this.clientSecret = options.clientSecret;
118
+ this.scope = options.scope ?? ['user.basic.read', 'user.establishment.read', 'user.booking.read'];
119
+ this.nonce = options.nonce ?? true;
120
+ this.tokenMinimumLifespan = options.tokenMinimumLifespan;
121
+ this.tokenFetcher = options.tokenFetcher ?? tokenFetcher;
122
+ }
123
+ /**
124
+ *
125
+ * @returns OpenIDConnectStrategy ready for use with passport
126
+ */
127
+ passportStrategy() {
128
+ return new OpenIDConnectStrategy({
129
+ issuer: this.launchpadAuthUrl,
130
+ authorizationURL: this.authorizationUrl,
131
+ tokenURL: this.tokenUrl,
132
+ callbackURL: this.callbackUrl,
133
+ clientID: this.clientId,
134
+ clientSecret: this.clientSecret,
135
+ scope: this.scope,
136
+ userInfoURL: '',
137
+ skipUserProfile: true,
138
+ nonce: this.nonce ? 'true' : undefined,
139
+ customHeaders: { authorization: this.authorizationToken() },
140
+ }, ((_issuer, _profile, _context, idToken, accessToken, refreshToken, done) => done(null, userFromTokens({ idToken, accessToken, refreshToken }))));
141
+ }
142
+ /**
143
+ * Checks the expiry of a given LaunchpadUser idToken and refreshToken.
144
+ * - If the idToken has expired it will attempt to refresh it using the refreshToken.
145
+ * - If the refreshToken has expired it will throw an Error.
146
+ *
147
+ * This method will always return a LaunchpadUser so it is suggested to updated your local copy and it will keep the tokens up to date.
148
+ *
149
+ * @param user - The LaunchpadUser instance to check token expiry on.
150
+ * @returns A LaunchpadUser is always returned, either the given user or a user with refreshed tokens.
151
+ * @throws Error is thrown if the refreshToken has expired, the user must sign in again from scratch.
152
+ */
153
+ async validateAndRefreshUser(user) {
154
+ const idToken = tokenFromJwt(user.idToken);
155
+ const refreshToken = tokenFromJwt(user.refreshToken);
156
+ const tokenExpiryTime = timeFromNow(this.tokenMinimumLifespan);
157
+ // Id Token still valid, return the current user
158
+ if (seconds(idToken.exp).isGreaterThanOrEqualTo(tokenExpiryTime)) {
159
+ return user;
160
+ }
161
+ // Refresh token expired, cannot continue
162
+ if (seconds(refreshToken.exp).isLessThan(tokenExpiryTime)) {
163
+ throw new Error('Refresh token expired');
164
+ }
165
+ // Refresh the user tokens
166
+ const newTokens = await this.tokenFetcher(user.refreshToken, this.tokenUrl, this.authorizationToken());
167
+ return userFromTokens(newTokens);
168
+ }
169
+ authorizationToken() {
170
+ return oauthClientToken(this.clientId, this.clientSecret);
171
+ }
172
+ }
173
+
174
+ export { PrisonerAuth, TimeSpan, milliseconds, minutes, nothing, seconds };
175
+ //# sourceMappingURL=index.esm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.esm.js","sources":["../src/main/tokens.ts","../src/main/launchpadUser.ts","../src/main/timeSpans.ts","../src/main/prisonerAuth.ts"],"sourcesContent":[null,null,null,null],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { default as PrisonerAuth, type PrisonerAuthOptions } from './prisonerAuth';
2
+ export { minutes, seconds, milliseconds, nothing, TimeSpan } from './timeSpans';
3
+ export type { LaunchpadUser } from './launchpadUser';
@@ -0,0 +1,11 @@
1
+ import OpenIDConnectStrategy from 'passport-openidconnect';
2
+ type Scope = 'user.basic.read' | 'user.establishment.read' | 'user.booking.read';
3
+ type LaunchpadAuthStrategyOptions = {
4
+ launchpadUrl: string;
5
+ clientID: string;
6
+ clientSecret: string;
7
+ callbackURL?: string;
8
+ scope?: Scope | Scope[];
9
+ };
10
+ export declare function launchpadAuthStrategy(options: LaunchpadAuthStrategyOptions): OpenIDConnectStrategy;
11
+ export {};