diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 36514da..9225785 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- node_version: ["12", "14"]
+ node_version: ["14"]
steps:
- uses: actions/checkout@v2
diff --git a/README.md b/README.md
index 11ff681..ecd7bf4 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,14 @@
- [Standalone usage](#standalone-usage)
- [Usage with Octokit](#usage-with-octokit)
- [`createOAuthUserClientAuth(options)` or `new Octokit({auth})`](#createoauthuserclientauthoptions-or-new-octokitauth)
+ - [Custom store](#custom-store)
+ - [Custom request](#custom-request)
- [`auth(command)`](#authcommand)
-- [Authentication object](#authentication-object)
+- [Session object](#session-object)
+ - [Authentication object](#authentication-object)
+ - [OAuth APP authentication token](#oauth-app-authentication-token)
+ - [GitHub APP user authentication token with expiring disabled](#github-app-user-authentication-token-with-expiring-disabled)
+ - [GitHub APP user authentication token with expiring enabled](#github-app-user-authentication-token-with-expiring-enabled)
- [`auth.hook(request, route, parameters)` or `auth.hook(request, options)`](#authhookrequest-route-parameters-or-authhookrequest-options)
- [Contributing](#contributing)
- [License](#license)
@@ -203,17 +209,17 @@ createOAuthAppAuth({
The async `auth()` method returned by `createOAuthUserClientAuth(options)` accepts the following commands:
-| Command | `{type: }` | Optional Arguments |
-| :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| [Sign in](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#1-request-a-users-github-identity) | `"signIn"` |
login: "user"allowSignup: falsescopes: ["repo"] (only relevant for OAuth Apps)
|
-| Get (local) token | `"getToken"` | – |
-| [Create an app token](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#2-users-are-redirected-back-to-your-site-by-github) | `"createToken"` | – |
-| [Check a token](https://docs.github.com/en/rest/reference/apps#check-a-token) | `"checkToken"` | – |
-| [Create a scoped access token](https://docs.github.com/en/rest/reference/apps#create-a-scoped-access-token) (for OAuth App) | `"createScopedToken"` | – |
-| [Reset a token](https://docs.github.com/en/rest/reference/apps#reset-a-token) | `"resetToken"` | – |
-| [Renewing a user token with a refresh token](https://docs.github.com/en/developers/apps/building-github-apps/reshing-user-to-server-access-tokens#renewing-a-user-token-with-a-refresh-token) (for GitHub App with token expiration enabled) | `"refreshToken"` | – |
-| [Delete an app token](https://docs.github.com/en/rest/reference/apps#delete-an-app-token) (sign out) | `"deleteToken"` | `offline: true` (only deletes session from local session store) |
-| [Delete an app authorization](https://docs.github.com/en/rest/reference/apps#delete-an-app-authorization) | `"deleteAuthorization"` | – |
+| Command | `{type: }` | Optional Arguments |
+| :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| [Sign in](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#1-request-a-users-github-identity) | `"signIn"` | login: "user"allowSignup: falsescopes: ["repo"] (only relevant for OAuth Apps)
|
+| Get (local) token | `"getToken"` | – |
+| [Create an app token](https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#2-users-are-redirected-back-to-your-site-by-github) | `"createToken"` | – |
+| [Check a token](https://docs.github.com/en/rest/reference/apps#check-a-token) | `"checkToken"` | – |
+| [Create a scoped access token](https://docs.github.com/en/rest/reference/apps#create-a-scoped-access-token) (for OAuth App) | `"createScopedToken"` | – |
+| [Reset a token](https://docs.github.com/en/rest/reference/apps#reset-a-token) | `"resetToken"` | – |
+| [Renewing a user token with a refresh token](https://docs.github.com/en/developers/apps/building-github-apps/refreshing-user-to-server-access-tokens#renewing-a-user-token-with-a-refresh-token) (for GitHub App with token expiration enabled) | `"refreshToken"` | – |
+| [Delete an app token](https://docs.github.com/en/rest/reference/apps#delete-an-app-token) (sign out) | `"deleteToken"` | `offline: true` (only deletes session from local session store) |
+| [Delete an app authorization](https://docs.github.com/en/rest/reference/apps#delete-an-app-authorization) | `"deleteAuthorization"` | – |
## Session object
diff --git a/src/auth.ts b/src/auth.ts
index 4a3de49..f947b17 100644
--- a/src/auth.ts
+++ b/src/auth.ts
@@ -77,8 +77,10 @@ export async function auth<
// Auto refresh for user-to-server token.
const expiresAt = this.session.authentication.expiresAt;
if (new Date(expiresAt) > new Date()) return this.session;
- // @ts-ignore
- return await auth.call(this, { type: "refreshToken" });
+ return await auth.call(this, { type: "refreshToken" } as Command<
+ Client,
+ Expiration
+ >);
}
}
@@ -118,6 +120,7 @@ export async function auth<
this.session ||= await auth.call(this);
if (!this.session) throw errors.unauthorized;
}
+ const oldSession = this.session;
// Prepare payload for `refreshToken` command.
if (this.session && "refreshToken" in this.session.authentication) {
@@ -133,6 +136,18 @@ export async function auth<
this.session = response.data || null;
}
+ // Some `oauth-app.js` endpoints (such as `resetToken`) do not (and can
+ // not) return `refreshToken`. Original `refreshToken` and
+ // `refreshTokenExpiresAt` are kept to `refreshToken` later.
+ if (oldSession && "refreshToken" in oldSession.authentication) {
+ if (this.session && !("refreshToken" in this.session.authentication))
+ Object.assign(this.session.authentication, {
+ refreshToken: oldSession.authentication.refreshToken,
+ refreshTokenExpiresAt:
+ oldSession.authentication.refreshTokenExpiresAt,
+ });
+ }
+
if (this.sessionStore) await this.sessionStore.set(this.session);
return this.session;
}
diff --git a/test/standalone.test.ts b/test/standalone.test.ts
index 6c2411c..225ac9b 100644
--- a/test/standalone.test.ts
+++ b/test/standalone.test.ts
@@ -663,4 +663,56 @@ describe("standalone tests under node environment", () => {
expect(sessionStore.set.mock.calls.length).toEqual(1);
expect(sessionStore.set.mock.calls[0][0]).toBeNull();
});
+
+ it("keeps refresh token", async () => {
+ const oldSession = {
+ authentication: {
+ token: "token123",
+ refreshToken: "refreshToken123",
+ refreshTokenExpiresAt: "2000-01-03T00:00:00.000Z",
+ },
+ };
+ const newSession = { authentication: { token: "token456" } };
+
+ const sessionStore = {
+ get: jest.fn().mockResolvedValue(oldSession),
+ set: jest.fn().mockResolvedValue(undefined),
+ };
+
+ const fetch = fetchMock
+ .sandbox()
+ .patchOnce("http://acme.com/api/github/oauth/token", newSession, {
+ headers: {
+ accept: "application/vnd.github.v3+json",
+ "user-agent": "test",
+ authorization: "token token123",
+ },
+ });
+
+ const auth = createOAuthUserClientAuth({
+ clientId: "clientId123",
+ sessionStore,
+ request: request.defaults({
+ headers: { "user-agent": "test" },
+ request: { fetch },
+ }),
+ });
+
+ expect(await auth({ type: "resetToken" })).toEqual({
+ authentication: {
+ token: "token456",
+ refreshToken: "refreshToken123",
+ refreshTokenExpiresAt: "2000-01-03T00:00:00.000Z",
+ },
+ });
+ expect(sessionStore.get.mock.calls.length).toBe(1);
+ expect(sessionStore.set.mock.calls.length).toBe(1);
+ expect(await auth()).toEqual({
+ authentication: {
+ token: "token456",
+ refreshToken: "refreshToken123",
+ refreshTokenExpiresAt: "2000-01-03T00:00:00.000Z",
+ },
+ });
+ });
});