Skip to content

Commit b94b043

Browse files
Introduce accessToken option to replace authorization
This is a better way to set the Authorization: request header – notice how the usage recommendations in the README look nicer now. Fixes #32. The old option is still supported, and you can also set both options together as long as they’re consistent (m3api-oauth2 will do this to stay forwards- and backwards-compatible). But as I’m not aware of any other uses of the authorization option (i.e. cases where you’d want a different scheme than “Bearer ${accessToken}”), I expect to remove it later.
1 parent 27200b8 commit b94b043

File tree

6 files changed

+159
-27
lines changed

6 files changed

+159
-27
lines changed

CHANGES.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ but this file may sometimes contain later improvements (e.g. typo fixes).
77

88
## next (not yet released)
99

10+
- The new `accessToken` option can be used to set the `Authorization` header more easily.
11+
The previous `authorization` option is kept for compatibility,
12+
but deprecated, demoted to the internal interface, and will be removed in a future minor version.
1013
- m3api now includes TypeScript `.d.ts` declaration files in the `types/` directory.
1114
Hopefully these will be useful to some users of the library.
1215
- Clarified `DEFAULT_OPTIONS` conventions a bit (adding `errorHandlers` is fine).

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -194,10 +194,10 @@ Other features not demonstrated above:
194194
This is mostly useful in library code, when you don’t know the `formatversion` of the response;
195195
you can import the helper from `core.js` (but not `browser.js` or `node.js`).
196196

197-
- The `authorization` request option can be used to set the `Authorization` request header.
197+
- The `accessToken` request option can be used to
198+
send an OAuth 2.0 access token in the `Authorization` request header.
198199
You can use this directly with an owner-only OAuth 2.0 client,
199-
by setting the option to the string `Bearer ACCESS_TOKEN`
200-
(where *ACCESS_TOKEN* is the access token MediaWiki generated for you);
200+
where MediaWiki will show you the access token right after registering the client;
201201
to use a regular OAuth 2.0 client and make requests authenticated as another user,
202202
use the [m3api-oauth2][] extension package.
203203

@@ -378,7 +378,7 @@ const session = new Session( 'en.wikipedia.org', { // or other domain
378378
}, {
379379
userAgent: TODO,
380380
maxRetriesSeconds: 3600, // or other high value
381-
authorization: `Bearer ${accessToken}`,
381+
accessToken: accessToken,
382382
} );
383383

384384
// ...
@@ -411,7 +411,7 @@ await session.request( {
411411

412412
- To authenticate the bot, create an owner-only OAuth 2 client,
413413
save the access token in a suitable secret store,
414-
specify <code>Bearer <var>accessToken</var></code> as the `authorization` option,
414+
set the `accessToken` option to the access token,
415415
and include `assert: 'user'` (and, if you like, <code>assertuser: '<var>user name</var>'</code>) in the default parameters.
416416
If the wiki you’re targeting doesn’t support OAuth 2,
417417
you may instead want to use the [m3api-botpassword][] extension package (see below).

core.js

Lines changed: 49 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -103,11 +103,17 @@
103103
* you don’t need to see this warning and can use this option to suppress it.
104104
* This option defaults to false in {@link Session#request} (i.e. treat the warning like any other),
105105
* but to true in {@link Session#requestAndContinueReducingBatch}.
106+
* @property {string} [accessToken] An OAuth 2.0 access token.
107+
* If this option is set, the Authorization request header will be set to `Bearer ${ accessToken }`.
108+
* You can get a token directly from an owner-only OAuth 2.0 client,
109+
* or you can use the m3api-oauth2 extension package to authorize a user via OAuth.
110+
* (In the latter case, you do not have to set this option: m3api-oauth2 will do it.)
106111
* @property {string} [authorization] Value for the Authorization request header.
107-
* This option can be used to authenticate requests using OAuth 2.0.
108-
* For an owner-only client / consumer, where you have an access token,
109-
* you can set this option to `Bearer ${ accessToken }` directly.
110-
* Otherwise, use the m3api-oauth2 extension package.
112+
* This option is deprecated in favor of accessToken above,
113+
* and only kept for backwards compatibility.
114+
* It is part of the internal interface, rather than the public interface,
115+
* and will be removed in a future minor version of m3api
116+
* (unless someone presents a use case for it that is not covered by the accessToken option).
111117
* @property {Object.<string, ErrorHandler>} [errorHandlers] Internal option.
112118
* Define handlers for API errors, which can retry the request if appropriate.
113119
* This option is only part of the internal interface, not of the stable, public interface.
@@ -186,6 +192,7 @@ const DEFAULT_OPTIONS = {
186192
retryAfterReadonlySeconds: 30,
187193
warn: console.warn,
188194
dropTruncatedResultWarning: false,
195+
accessToken: null,
189196
authorization: null,
190197
errorHandlers: {
191198
maxlag: ( session, params, options, internalResponse, error ) => {
@@ -722,11 +729,7 @@ class Session {
722729
const requestHeaders = {
723730
'user-agent': this.getUserAgent( options ),
724731
};
725-
const { authorization } = {
726-
...DEFAULT_OPTIONS,
727-
...this.defaultOptions,
728-
...options,
729-
};
732+
const authorization = this.getAuthorizationHeader( options );
730733
if ( authorization ) {
731734
requestHeaders.authorization = authorization;
732735
}
@@ -762,6 +765,43 @@ class Session {
762765
}
763766
}
764767

768+
/**
769+
* Get the Authorization: header for these options.
770+
*
771+
* @protected
772+
* @param {Options} options
773+
* @return {string|null}
774+
*/
775+
getAuthorizationHeader( options ) {
776+
const {
777+
accessToken,
778+
authorization,
779+
} = {
780+
...DEFAULT_OPTIONS,
781+
...this.defaultOptions,
782+
...options,
783+
};
784+
785+
if ( accessToken ) {
786+
const expectedAuthorization = `Bearer ${ accessToken }`;
787+
if ( authorization ) {
788+
if ( authorization === expectedAuthorization ) {
789+
return authorization;
790+
} else {
791+
throw new Error(
792+
`Inconsistent authorization and accessToken options: ${ authorization } != ${ expectedAuthorization }`,
793+
);
794+
}
795+
} else {
796+
return expectedAuthorization;
797+
}
798+
} else if ( authorization ) {
799+
return authorization;
800+
} else {
801+
return null;
802+
}
803+
}
804+
765805
/**
766806
* @private
767807
* @param {Params} params

test/unit/core.test.js

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -606,14 +606,14 @@ describe( 'Session', () => {
606606
} );
607607

608608
for ( const [ name, defaultOptions, options ] of [
609-
[ 'defaultOptions', { authorization: 'the-authorization-header' }, {} ],
610-
[ 'options', {}, { authorization: 'the-authorization-header' } ],
609+
[ 'accessToken in defaultOptions', { accessToken: 'the-access-token' }, {} ],
610+
[ 'accessToken in options', {}, { accessToken: 'the-access-token' } ],
611611
] ) {
612-
it( `in ${ name }`, async () => {
612+
it( name, async () => {
613613
let called = false;
614614
class TestSession extends BaseTestSession {
615615
async internalGet( apiUrl, params, { authorization } ) {
616-
expect( authorization ).to.equal( 'the-authorization-header' );
616+
expect( authorization ).to.equal( 'Bearer the-access-token' );
617617
expect( called, 'not called yet' ).to.be.false;
618618
called = true;
619619
return {
@@ -629,6 +629,72 @@ describe( 'Session', () => {
629629
expect( called ).to.be.true;
630630
} );
631631
}
632+
633+
for ( const [ name, defaultOptions, options ] of [
634+
[ 'authorization in defaultOptions', { authorization: 'the-authorization' }, {} ],
635+
[ 'authorization in options', {}, { authorization: 'the-authorization' } ],
636+
] ) {
637+
it( name, async () => {
638+
let called = false;
639+
class TestSession extends BaseTestSession {
640+
async internalGet( apiUrl, params, { authorization } ) {
641+
expect( authorization ).to.equal( 'the-authorization' );
642+
expect( called, 'not called yet' ).to.be.false;
643+
called = true;
644+
return {
645+
status: 200,
646+
headers: {},
647+
body: { response: true },
648+
};
649+
}
650+
}
651+
652+
const session = new TestSession( 'en.wikipedia.org', {}, defaultOptions );
653+
await session.request( {}, options );
654+
expect( called ).to.be.true;
655+
} );
656+
}
657+
658+
it( 'accessToken consistent with authorization', async () => {
659+
let called = false;
660+
class TestSession extends BaseTestSession {
661+
async internalGet( apiUrl, params, { authorization } ) {
662+
expect( authorization ).to.equal( 'Bearer the-access-token' );
663+
expect( called, 'not called yet' ).to.be.false;
664+
called = true;
665+
return {
666+
status: 200,
667+
headers: {},
668+
body: { response: true },
669+
};
670+
}
671+
}
672+
673+
const session = new TestSession( 'en.wikipedia.org' );
674+
await session.request( {}, {
675+
authorization: 'Bearer the-access-token',
676+
accessToken: 'the-access-token',
677+
} );
678+
expect( called ).to.be.true;
679+
} );
680+
681+
it( 'accessToken inconsistent with authorization', async () => {
682+
let called = false;
683+
class TestSession extends BaseTestSession {
684+
async internalGet() {
685+
called = true;
686+
throw new Error( 'Should not be called in this test' );
687+
}
688+
}
689+
690+
const session = new TestSession( 'en.wikipedia.org', {}, {
691+
authorization: 'the-authorization',
692+
accessToken: 'the-access-token',
693+
} );
694+
await expect( session.request( {} ) )
695+
.to.be.rejected;
696+
expect( called ).to.be.false;
697+
} );
632698
} );
633699

634700
describe( 'automatic retry', () => {

types/core.d.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,21 @@ export type Options = {
109109
* but to true in {@link Session#requestAndContinueReducingBatch}.
110110
*/
111111
dropTruncatedResultWarning?: boolean;
112+
/**
113+
* An OAuth 2.0 access token.
114+
* If this option is set, the Authorization request header will be set to `Bearer ${ accessToken }`.
115+
* You can get a token directly from an owner-only OAuth 2.0 client,
116+
* or you can use the m3api-oauth2 extension package to authorize a user via OAuth.
117+
* (In the latter case, you do not have to set this option: m3api-oauth2 will do it.)
118+
*/
119+
accessToken?: string;
112120
/**
113121
* Value for the Authorization request header.
114-
* This option can be used to authenticate requests using OAuth 2.0.
115-
* For an owner-only client / consumer, where you have an access token,
116-
* you can set this option to `Bearer ${ accessToken }` directly.
117-
* Otherwise, use the m3api-oauth2 extension package.
122+
* This option is deprecated in favor of accessToken above,
123+
* and only kept for backwards compatibility.
124+
* It is part of the internal interface, rather than the public interface,
125+
* and will be removed in a future minor version of m3api
126+
* (unless someone presents a use case for it that is not covered by the accessToken option).
118127
*/
119128
authorization?: string;
120129
/**
@@ -257,11 +266,17 @@ export type InternalResponse = {
257266
* you don’t need to see this warning and can use this option to suppress it.
258267
* This option defaults to false in {@link Session#request} (i.e. treat the warning like any other),
259268
* but to true in {@link Session#requestAndContinueReducingBatch}.
269+
* @property {string} [accessToken] An OAuth 2.0 access token.
270+
* If this option is set, the Authorization request header will be set to `Bearer ${ accessToken }`.
271+
* You can get a token directly from an owner-only OAuth 2.0 client,
272+
* or you can use the m3api-oauth2 extension package to authorize a user via OAuth.
273+
* (In the latter case, you do not have to set this option: m3api-oauth2 will do it.)
260274
* @property {string} [authorization] Value for the Authorization request header.
261-
* This option can be used to authenticate requests using OAuth 2.0.
262-
* For an owner-only client / consumer, where you have an access token,
263-
* you can set this option to `Bearer ${ accessToken }` directly.
264-
* Otherwise, use the m3api-oauth2 extension package.
275+
* This option is deprecated in favor of accessToken above,
276+
* and only kept for backwards compatibility.
277+
* It is part of the internal interface, rather than the public interface,
278+
* and will be removed in a future minor version of m3api
279+
* (unless someone presents a use case for it that is not covered by the accessToken option).
265280
* @property {Object.<string, ErrorHandler>} [errorHandlers] Internal option.
266281
* Define handlers for API errors, which can retry the request if appropriate.
267282
* This option is only part of the internal interface, not of the stable, public interface.
@@ -512,6 +527,14 @@ export class Session {
512527
protected getUserAgent(options: Options): string;
513528
/** @private */
514529
private warnedDefaultUserAgent;
530+
/**
531+
* Get the Authorization: header for these options.
532+
*
533+
* @protected
534+
* @param {Options} options
535+
* @return {string|null}
536+
*/
537+
protected getAuthorizationHeader(options: Options): string | null;
515538
/**
516539
* @private
517540
* @param {Params} params

types/core.d.ts.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)