Skip to content

Commit dde5d7f

Browse files
authored
Merge branch 'master' into cleanup-code-verifier
2 parents a43c632 + 9748dd9 commit dde5d7f

13 files changed

+321
-181
lines changed

.github/workflows/ci.yml

+7-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ jobs:
1515
strategy:
1616
matrix:
1717
os: [ubuntu-latest]
18-
node: ['18', '20']
18+
node: ["18", "20"]
1919

2020
runs-on: ${{ matrix.os }}
2121

@@ -35,3 +35,9 @@ jobs:
3535
- name: Run tests
3636
run: |
3737
npm t
38+
39+
- name: Upload coverage results to Coveralls
40+
uses: coverallsapp/github-action@master
41+
with:
42+
github-token: ${{ secrets.GITHUB_TOKEN }}
43+
path-to-lcov: ./test/coverage/lcov.info

.github/workflows/dogfooding.yml

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Dogfooding Check
2+
3+
on:
4+
pull_request_review:
5+
types: [submitted, edited]
6+
7+
pull_request_target:
8+
types:
9+
- opened
10+
branches:
11+
- '*'
12+
13+
jobs:
14+
check_dogfooding:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- uses: actions/checkout@v4
18+
if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.head.ref == 'release-please--branches--master'
19+
with:
20+
ref: master # used to identify the latest RC version via git describe --tags --match rc*
21+
fetch-depth: 0
22+
23+
- if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.head.ref == 'release-please--branches--master'
24+
run: |
25+
set -ex
26+
27+
# finds the latest RC version on master
28+
RELEASE_VERSION=@supabase/auth-js@$(node -e "const a = '$(git describe --tags --match rc*)'.replace(/^rc/, '').split('-'); console.log(a[0] + '-' + a[1]);")
29+
30+
# use some clever Ruby magic to extract the snapshots['@supabase/auth-js@...'] version from the pnpm-lock.yaml file
31+
STUDIO_VERSION=$(curl 'https://raw.githubusercontent.com/supabase/supabase/refs/heads/master/pnpm-lock.yaml' | ruby -e 'require("yaml"); l = YAML.load(STDIN); puts(l["snapshots"].find { |k, v| k.start_with? "@supabase/auth-js" }.first)')
32+
33+
echo "Expecting RC version $RELEASE_VERSION to be used in Supabase Studio."
34+
35+
if [ "$STUDIO_VERSION" != "$RELEASE_VERSION" ]
36+
then
37+
echo "Version in Supabase Studio is not the latest release candidate. Please release this RC first to proof the release before merging this PR."
38+
exit 1
39+
fi
40+
41+
echo "Release away!"
42+
exit 0
43+
44+
- if: github.event.pull_request.base.ref != 'master' || github.event.pull_request.head.ref != 'release-please--branches--master'
45+
run: |
46+
set -ex
47+
48+
echo "This PR is not subject to dogfooding checks."
49+
exit 0
50+

.github/workflows/release.yml

+5-3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
release_please:
1111
runs-on: ubuntu-latest
1212
permissions:
13+
id-token: write
1314
contents: write
1415
pull-requests: write
1516
steps:
@@ -102,16 +103,17 @@ jobs:
102103
103104
echo "Publishing auth-js now..."
104105
105-
npm publish --tag "$DIST_TAG"
106+
npm publish --provenance --tag "$DIST_TAG"
106107
107108
echo "Publishing gotrue-js now..."
108109
109110
for f in package.json package-lock.json
110111
do
111-
sed -i 's|\(["/]\)auth-js|\1gotrue-js|g' "$f"
112+
# only replace name not repository, homepage, etc.
113+
sed -i 's|\("name":[[:space:]]*"@supabase/\)auth-js|\1gotrue-js|g' "$f"
112114
done
113115
114-
npm publish --tag "$DIST_TAG"
116+
npm publish --provenance --tag "$DIST_TAG"
115117
116118
- name: Create GitHub release and branches
117119
if: ${{ steps.release.outputs.release_created == 'true' || steps.release.outputs.prs_created == 'true' }}

CHANGELOG.md

+43
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
# Changelog
22

3+
## [2.67.3](https://github.com/supabase/auth-js/compare/v2.67.2...v2.67.3) (2024-12-17)
4+
5+
6+
### Bug Fixes
7+
8+
* return redirect errors early ([#1003](https://github.com/supabase/auth-js/issues/1003)) ([9751b80](https://github.com/supabase/auth-js/commit/9751b8029b4235a63dcb525e7ce7cc942c85daf5))
9+
10+
## [2.67.2](https://github.com/supabase/auth-js/compare/v2.67.1...v2.67.2) (2024-12-16)
11+
12+
13+
### Bug Fixes
14+
15+
* `isBrowser()` to include check on `window` ([#982](https://github.com/supabase/auth-js/issues/982)) ([645f224](https://github.com/supabase/auth-js/commit/645f22447e68ba13e43e359d1524e95fe025d771))
16+
17+
## [2.67.1](https://github.com/supabase/auth-js/compare/v2.67.0...v2.67.1) (2024-12-13)
18+
19+
20+
### Bug Fixes
21+
22+
* revert [#992](https://github.com/supabase/auth-js/issues/992) and [#993](https://github.com/supabase/auth-js/issues/993) ([#999](https://github.com/supabase/auth-js/issues/999)) ([12b2848](https://github.com/supabase/auth-js/commit/12b2848237854f3d70b9989920ad50e2c4186fff))
23+
24+
## [2.67.0](https://github.com/supabase/auth-js/compare/v2.66.1...v2.67.0) (2024-12-12)
25+
26+
27+
### Features
28+
29+
* wrap navigator.locks.request with plain promise to help zone.js ([#989](https://github.com/supabase/auth-js/issues/989)) ([2e6e07c](https://github.com/supabase/auth-js/commit/2e6e07c21a561ca13d5e74b69609c2cc93f104f4)), closes [#830](https://github.com/supabase/auth-js/issues/830)
30+
31+
32+
### Bug Fixes
33+
34+
* add email_address_invalid error code ([#994](https://github.com/supabase/auth-js/issues/994)) ([232f133](https://github.com/supabase/auth-js/commit/232f133b1a84b4c667e994f472098aa5cde2088d))
35+
* return error early for redirects ([#992](https://github.com/supabase/auth-js/issues/992)) ([9f32d30](https://github.com/supabase/auth-js/commit/9f32d30e17954c5d4320b374a108617cda5ab357))
36+
37+
## [2.66.1](https://github.com/supabase/auth-js/compare/v2.66.0...v2.66.1) (2024-12-04)
38+
39+
40+
### Bug Fixes
41+
42+
* add loose auto complete to string literals where applicable ([#966](https://github.com/supabase/auth-js/issues/966)) ([fd9248d](https://github.com/supabase/auth-js/commit/fd9248d7aecd0bd00381dff162969d8014a3359a))
43+
* add new error codes ([#979](https://github.com/supabase/auth-js/issues/979)) ([dfb40d2](https://github.com/supabase/auth-js/commit/dfb40d24188f7e8b0d34e51ded15582086250c51))
44+
* don't remove session for identity linking errors ([#987](https://github.com/supabase/auth-js/issues/987)) ([e68ebe6](https://github.com/supabase/auth-js/commit/e68ebe604d15d881b23678d180cccb7115f16f4e))
45+
346
## [2.66.0](https://github.com/supabase/auth-js/compare/v2.65.1...v2.66.0) (2024-11-01)
447

548

example/react/package-lock.json

+6-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"main": "dist/main/index.js",
2121
"module": "dist/module/index.js",
2222
"types": "dist/module/index.d.ts",
23-
"repository": "supabase/auth-js",
23+
"repository": "github:supabase/auth-js",
2424
"scripts": {
2525
"clean": "rimraf dist docs",
2626
"coverage": "echo \"run npm test\"",
@@ -30,7 +30,7 @@
3030
"build:module": "tsc -p tsconfig.module.json",
3131
"lint": "eslint ./src/**/* test/**/*.test.ts",
3232
"test": "run-s test:clean test:infra test:suite test:clean",
33-
"test:suite": "jest --runInBand",
33+
"test:suite": "jest --runInBand --coverage",
3434
"test:infra": "cd infra && docker compose down && docker compose pull && docker compose up -d && sleep 30",
3535
"test:clean": "cd infra && docker compose down",
3636
"docs": "typedoc src/index.ts --out docs/v2 --excludePrivate --excludeProtected",

src/GoTrueClient.ts

+64-34
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import {
1212
isAuthError,
1313
isAuthRetryableFetchError,
1414
isAuthSessionMissingError,
15+
isAuthImplicitGrantRedirectError,
1516
} from './lib/errors'
1617
import {
1718
Fetch,
@@ -88,13 +89,11 @@ import type {
8889
LockFunc,
8990
UserIdentity,
9091
SignInAnonymouslyCredentials,
91-
} from './lib/types'
92-
import {
9392
MFAEnrollTOTPParams,
9493
MFAEnrollPhoneParams,
9594
AuthMFAEnrollTOTPResponse,
9695
AuthMFAEnrollPhoneResponse,
97-
} from './lib/internal-types'
96+
} from './lib/types'
9897

9998
polyfillGlobalThis() // Make "globalThis" available
10099

@@ -306,16 +305,34 @@ export default class GoTrueClient {
306305
*/
307306
private async _initialize(): Promise<InitializeResult> {
308307
try {
309-
const isPKCEFlow = isBrowser() ? await this._isPKCEFlow() : false
310-
this._debug('#_initialize()', 'begin', 'is PKCE flow', isPKCEFlow)
311-
312-
if (isPKCEFlow || (this.detectSessionInUrl && this._isImplicitGrantFlow())) {
313-
const { data, error } = await this._getSessionFromURL(isPKCEFlow)
308+
const params = parseParametersFromURL(window.location.href)
309+
let callbackUrlType = 'none'
310+
if (this._isImplicitGrantCallback(params)) {
311+
callbackUrlType = 'implicit'
312+
} else if (await this._isPKCECallback(params)) {
313+
callbackUrlType = 'pkce'
314+
}
315+
316+
/**
317+
* Attempt to get the session from the URL only if these conditions are fulfilled
318+
*
319+
* Note: If the URL isn't one of the callback url types (implicit or pkce),
320+
* then there could be an existing session so we don't want to prematurely remove it
321+
*/
322+
if (isBrowser() && this.detectSessionInUrl && callbackUrlType !== 'none') {
323+
const { data, error } = await this._getSessionFromURL(params, callbackUrlType)
314324
if (error) {
315325
this._debug('#_initialize()', 'error detecting session from URL', error)
316326

317-
if (error?.code === 'identity_already_exists') {
318-
return { error }
327+
if (isAuthImplicitGrantRedirectError(error)) {
328+
const errorCode = error.details?.code
329+
if (
330+
errorCode === 'identity_already_exists' ||
331+
errorCode === 'identity_not_found' ||
332+
errorCode === 'single_identity_not_deletable'
333+
) {
334+
return { error }
335+
}
319336
}
320337

321338
// failed login attempt via url,
@@ -1419,7 +1436,10 @@ export default class GoTrueClient {
14191436
/**
14201437
* Gets the session data from a URL string
14211438
*/
1422-
private async _getSessionFromURL(isPKCEFlow: boolean): Promise<
1439+
private async _getSessionFromURL(
1440+
params: { [parameter: string]: string },
1441+
callbackUrlType: string
1442+
): Promise<
14231443
| {
14241444
data: { session: Session; redirectType: string | null }
14251445
error: null
@@ -1428,15 +1448,39 @@ export default class GoTrueClient {
14281448
> {
14291449
try {
14301450
if (!isBrowser()) throw new AuthImplicitGrantRedirectError('No browser detected.')
1431-
if (this.flowType === 'implicit' && !this._isImplicitGrantFlow()) {
1432-
throw new AuthImplicitGrantRedirectError('Not a valid implicit grant flow url.')
1433-
} else if (this.flowType == 'pkce' && !isPKCEFlow) {
1434-
throw new AuthPKCEGrantCodeExchangeError('Not a valid PKCE flow url.')
1451+
1452+
// If there's an error in the URL, it doesn't matter what flow it is, we just return the error.
1453+
if (params.error || params.error_description || params.error_code) {
1454+
// The error class returned implies that the redirect is from an implicit grant flow
1455+
// but it could also be from a redirect error from a PKCE flow.
1456+
throw new AuthImplicitGrantRedirectError(
1457+
params.error_description || 'Error in URL with unspecified error_description',
1458+
{
1459+
error: params.error || 'unspecified_error',
1460+
code: params.error_code || 'unspecified_code',
1461+
}
1462+
)
14351463
}
14361464

1437-
const params = parseParametersFromURL(window.location.href)
1465+
// Checks for mismatches between the flowType initialised in the client and the URL parameters
1466+
switch (callbackUrlType) {
1467+
case 'implicit':
1468+
if (this.flowType === 'pkce') {
1469+
throw new AuthPKCEGrantCodeExchangeError('Not a valid PKCE flow url.')
1470+
}
1471+
break
1472+
case 'pkce':
1473+
if (this.flowType === 'implicit') {
1474+
throw new AuthImplicitGrantRedirectError('Not a valid implicit grant flow url.')
1475+
}
1476+
break
1477+
default:
1478+
// there's no mismatch so we continue
1479+
}
14381480

1439-
if (isPKCEFlow) {
1481+
// Since this is a redirect for PKCE, we attempt to retrieve the code from the URL for the code exchange
1482+
if (callbackUrlType === 'pkce') {
1483+
this._debug('#_initialize()', 'begin', 'is PKCE flow', true)
14401484
if (!params.code) throw new AuthPKCEGrantCodeExchangeError('No code detected.')
14411485
const { data, error } = await this._exchangeCodeForSession(params.code)
14421486
if (error) throw error
@@ -1449,16 +1493,6 @@ export default class GoTrueClient {
14491493
return { data: { session: data.session, redirectType: null }, error: null }
14501494
}
14511495

1452-
if (params.error || params.error_description || params.error_code) {
1453-
throw new AuthImplicitGrantRedirectError(
1454-
params.error_description || 'Error in URL with unspecified error_description',
1455-
{
1456-
error: params.error || 'unspecified_error',
1457-
code: params.error_code || 'unspecified_code',
1458-
}
1459-
)
1460-
}
1461-
14621496
const {
14631497
provider_token,
14641498
provider_refresh_token,
@@ -1536,18 +1570,14 @@ export default class GoTrueClient {
15361570
/**
15371571
* Checks if the current URL contains parameters given by an implicit oauth grant flow (https://www.rfc-editor.org/rfc/rfc6749.html#section-4.2)
15381572
*/
1539-
private _isImplicitGrantFlow(): boolean {
1540-
const params = parseParametersFromURL(window.location.href)
1541-
1542-
return !!(isBrowser() && (params.access_token || params.error_description))
1573+
private _isImplicitGrantCallback(params: { [parameter: string]: string }): boolean {
1574+
return Boolean(params.access_token || params.error_description)
15431575
}
15441576

15451577
/**
15461578
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
15471579
*/
1548-
private async _isPKCEFlow(): Promise<boolean> {
1549-
const params = parseParametersFromURL(window.location.href)
1550-
1580+
private async _isPKCECallback(params: { [parameter: string]: string }): Promise<boolean> {
15511581
const currentStorageContent = await getItemAsync(
15521582
this.storage,
15531583
`${this.storageKey}-code-verifier`

src/lib/error-codes.ts

+1
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,4 @@ export type ErrorCode =
8787
| 'mfa_verified_factor_exists'
8888
| 'invalid_credentials'
8989
| 'email_address_not_authorized'
90+
| 'email_address_invalid'

src/lib/errors.ts

+6
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,12 @@ export class AuthImplicitGrantRedirectError extends CustomAuthError {
102102
}
103103
}
104104

105+
export function isAuthImplicitGrantRedirectError(
106+
error: any
107+
): error is AuthImplicitGrantRedirectError {
108+
return isAuthError(error) && error.name === 'AuthImplicitGrantRedirectError'
109+
}
110+
105111
export class AuthPKCEGrantCodeExchangeError extends CustomAuthError {
106112
details: { error: string; code: string } | null = null
107113

src/lib/helpers.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export function uuid() {
1414
})
1515
}
1616

17-
export const isBrowser = () => typeof document !== 'undefined'
17+
export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined'
1818

1919
const localStorageWriteTests = {
2020
tested: false,

0 commit comments

Comments
 (0)