• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

u-wave / core / 11085094286

28 Sep 2024 03:39PM UTC coverage: 79.715% (-0.4%) from 80.131%
11085094286

Pull #637

github

web-flow
Merge 11ccf3b06 into 14c162f19
Pull Request #637: Switch to a relational database, closes #549

751 of 918 branches covered (81.81%)

Branch coverage included in aggregate %.

1891 of 2530 new or added lines in 50 files covered. (74.74%)

13 existing lines in 7 files now uncovered.

9191 of 11554 relevant lines covered (79.55%)

68.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.83
/src/plugins/passport.js
1
import fs from 'node:fs';
1✔
2
import { Passport } from 'passport';
1✔
3
import { Strategy as LocalStrategy } from 'passport-local';
1✔
4
import { Strategy as GoogleStrategy } from 'passport-google-oauth20';
1✔
5
import { callbackify } from 'util';
1✔
6
import JWTStrategy from '../auth/JWTStrategy.js';
1✔
7

1✔
8
const schema = JSON.parse(
1✔
9
  fs.readFileSync(new URL('../schemas/socialAuth.json', import.meta.url), 'utf8'),
1✔
10
);
1✔
11

1✔
12
/**
1✔
13
 * @typedef {import('../schema.js').UserID} UserID
1✔
14
 * @typedef {import('../schema.js').User} User
1✔
15
 * @typedef {{
1✔
16
 *   callbackURL?: string,
1✔
17
 * } & ({
1✔
18
 *   enabled: false,
1✔
19
 * } | {
1✔
20
 *   enabled: true,
1✔
21
 *   clientID: string,
1✔
22
 *   clientSecret: string,
1✔
23
 * })} GoogleOptions
1✔
24
 * @typedef {object} SocialAuthSettings
1✔
25
 * @prop {GoogleOptions} google
1✔
26
 */
1✔
27

1✔
28
class PassportPlugin extends Passport {
92✔
29
  #uw;
92✔
30

92✔
31
  #logger;
92✔
32

92✔
33
  /**
92✔
34
   * @param {import('../Uwave.js').Boot} uw
92✔
35
   * @param {{ secret: Buffer|string }} options
92✔
36
   */
92✔
37
  constructor(uw, options) {
92✔
38
    super();
92✔
39

92✔
40
    this.#uw = uw;
92✔
41
    this.#logger = uw.logger.child({ ns: 'uwave:authentication' });
92✔
42

92✔
43
    /**
92✔
44
     * @param {Express.User} user
92✔
45
     * @returns {Promise<UserID>}
92✔
46
     */
92✔
47
    function serializeUser(user) {
92✔
48
      /** @type {UserID} */
115✔
49
      // @ts-expect-error `user` is actually an instance of the User model
115✔
50
      // but we can't express that
115✔
51
      const userID = user.id;
115✔
52
      return Promise.resolve(userID);
115✔
53
    }
115✔
54
    /**
92✔
55
     * @param {UserID} id
92✔
56
     * @returns {Promise<User|null>}
92✔
57
     */
92✔
58
    function deserializeUser(id) {
92✔
59
      return uw.users.getUser(id);
×
60
    }
×
61

92✔
62
    this.serializeUser(callbackify(serializeUser));
92✔
63
    this.deserializeUser(callbackify(deserializeUser));
92✔
64

92✔
65
    /**
92✔
66
     * @param {string} email
92✔
67
     * @param {string} password
92✔
68
     * @returns {Promise<User>}
92✔
69
     */
92✔
70
    function localLogin(email, password) {
92✔
71
      return uw.users.login({ type: 'local', email, password });
×
72
    }
×
73

92✔
74
    this.use('local', new LocalStrategy({
92✔
75
      usernameField: 'email',
92✔
76
      passwordField: 'password',
92✔
77
      session: false,
92✔
78
    }, callbackify(localLogin)));
92✔
79
    this.use('jwt', new JWTStrategy(options.secret, async (claim) => {
92✔
80
      try {
115✔
81
        return await uw.users.getUser(claim.id);
115✔
82
      } catch (err) {
115!
NEW
83
        this.#logger.warn({ err, claim }, 'could not load user from JWT');
×
84
        return null;
×
85
      }
×
86
    }));
92✔
87

92✔
88
    uw.config.register(schema['uw:key'], schema);
92✔
89
    const unsubscribe = uw.config.subscribe(schema['uw:key'], /** @param {SocialAuthSettings} settings */ (settings) => {
92✔
90
      this.applyAuthStrategies(settings);
1✔
91
    });
92✔
92
    uw.onClose(unsubscribe);
92✔
93
  }
92✔
94

92✔
95
  /**
92✔
96
   * Must be called once on boot.
92✔
97
   */
92✔
98
  async loadRuntimeConfiguration() {
92✔
99
    /** @type {SocialAuthSettings} */
92✔
100
    // @ts-expect-error TS2322 `get()` returns a validated object with default values populated
92✔
101
    const settings = await this.#uw.config.get(schema['uw:key']);
92✔
102
    try {
92✔
103
      this.applyAuthStrategies(settings);
92✔
104
    } catch (error) {
92!
105
      // The schema doesn't _quite_ protect against all possible misconfiguration
×
106
      this.#logger.error({ err: error }, 'applying social auth settings failed');
×
107
    }
×
108
  }
92✔
109

92✔
110
  /**
92✔
111
   * @param {string} accessToken Not used as we do not need to access the account.
92✔
112
   * @param {string} refreshToken Not used as we do not need to access the account.
92✔
113
   * @param {import('passport').Profile} profile
92✔
114
   * @returns {Promise<User>}
92✔
115
   * @private
92✔
116
   */
92✔
117
  socialLogin(accessToken, refreshToken, profile) {
92✔
118
    return this.#uw.users.login({
×
119
      type: profile.provider,
×
120
      profile,
×
121
    });
×
122
  }
×
123

92✔
124
  /**
92✔
125
   * @param {string} strategy
92✔
126
   * @returns {boolean}
92✔
127
   */
92✔
128
  supports(strategy) {
92✔
129
    // @ts-expect-error TS2339: _strategy is not in the typings for passport but it does exist
×
130
    // eslint-disable-next-line no-underscore-dangle
×
131
    return this._strategy(strategy) !== undefined;
×
132
  }
×
133

92✔
134
  /**
92✔
135
   * @returns {string[]}
92✔
136
   */
92✔
137
  strategies() {
92✔
138
    // @ts-expect-error TS2339: _strategies is not in the typings for passport but it does exist
5✔
139
    // eslint-disable-next-line no-underscore-dangle
5✔
140
    return Object.keys(this._strategies)
5✔
141
      .filter((strategy) => strategy !== 'session' && strategy !== 'jwt');
5✔
142
  }
5✔
143

92✔
144
  /**
92✔
145
   * @param {SocialAuthSettings} settings
92✔
146
   * @private
92✔
147
   */
92✔
148
  applyAuthStrategies(settings) {
92✔
149
    this.#logger.info('reapplying settings');
93✔
150
    this.unuse('google');
93✔
151

93✔
152
    if (settings && settings.google && settings.google.enabled) {
93✔
153
      this.#logger.info('enable google');
1✔
154
      this.use('google', new GoogleStrategy({
1✔
155
        callbackURL: '/auth/service/google/callback',
1✔
156
        ...settings.google,
1✔
157
        scope: ['profile'],
1✔
158
      }, callbackify(this.socialLogin.bind(this))));
1✔
159
    }
1✔
160
  }
93✔
161
}
92✔
162

1✔
163
/**
1✔
164
 * @param {import('../Uwave.js').Boot} uw
1✔
165
 * @param {{ secret: Buffer|string }} options
1✔
166
 */
1✔
167
async function passportPlugin(uw, options) {
92✔
168
  uw.passport = new PassportPlugin(uw, options);
92✔
169
  await uw.passport.loadRuntimeConfiguration();
92✔
170
}
92✔
171

1✔
172
export default passportPlugin;
1✔
173
export { PassportPlugin as Passport };
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc