Skip to content

Commit

Permalink
Merge branch 'master' into cleanup-code-verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
j4w8n authored Jan 14, 2025
2 parents a43c632 + 9748dd9 commit dde5d7f
Show file tree
Hide file tree
Showing 13 changed files with 321 additions and 181 deletions.
8 changes: 7 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
node: ['18', '20']
node: ["18", "20"]

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

Expand All @@ -35,3 +35,9 @@ jobs:
- name: Run tests
run: |
npm t
- name: Upload coverage results to Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./test/coverage/lcov.info
50 changes: 50 additions & 0 deletions .github/workflows/dogfooding.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Dogfooding Check

on:
pull_request_review:
types: [submitted, edited]

pull_request_target:
types:
- opened
branches:
- '*'

jobs:
check_dogfooding:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.head.ref == 'release-please--branches--master'
with:
ref: master # used to identify the latest RC version via git describe --tags --match rc*
fetch-depth: 0

- if: github.event.pull_request.base.ref == 'master' && github.event.pull_request.head.ref == 'release-please--branches--master'
run: |
set -ex
# finds the latest RC version on master
RELEASE_VERSION=@supabase/auth-js@$(node -e "const a = '$(git describe --tags --match rc*)'.replace(/^rc/, '').split('-'); console.log(a[0] + '-' + a[1]);")
# use some clever Ruby magic to extract the snapshots['@supabase/auth-js@...'] version from the pnpm-lock.yaml file
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)')
echo "Expecting RC version $RELEASE_VERSION to be used in Supabase Studio."
if [ "$STUDIO_VERSION" != "$RELEASE_VERSION" ]
then
echo "Version in Supabase Studio is not the latest release candidate. Please release this RC first to proof the release before merging this PR."
exit 1
fi
echo "Release away!"
exit 0
- if: github.event.pull_request.base.ref != 'master' || github.event.pull_request.head.ref != 'release-please--branches--master'
run: |
set -ex
echo "This PR is not subject to dogfooding checks."
exit 0
8 changes: 5 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ jobs:
release_please:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
pull-requests: write
steps:
Expand Down Expand Up @@ -102,16 +103,17 @@ jobs:
echo "Publishing auth-js now..."
npm publish --tag "$DIST_TAG"
npm publish --provenance --tag "$DIST_TAG"
echo "Publishing gotrue-js now..."
for f in package.json package-lock.json
do
sed -i 's|\(["/]\)auth-js|\1gotrue-js|g' "$f"
# only replace name not repository, homepage, etc.
sed -i 's|\("name":[[:space:]]*"@supabase/\)auth-js|\1gotrue-js|g' "$f"
done
npm publish --tag "$DIST_TAG"
npm publish --provenance --tag "$DIST_TAG"
- name: Create GitHub release and branches
if: ${{ steps.release.outputs.release_created == 'true' || steps.release.outputs.prs_created == 'true' }}
Expand Down
43 changes: 43 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,48 @@
# Changelog

## [2.67.3](https://github.com/supabase/auth-js/compare/v2.67.2...v2.67.3) (2024-12-17)


### Bug Fixes

* return redirect errors early ([#1003](https://github.com/supabase/auth-js/issues/1003)) ([9751b80](https://github.com/supabase/auth-js/commit/9751b8029b4235a63dcb525e7ce7cc942c85daf5))

## [2.67.2](https://github.com/supabase/auth-js/compare/v2.67.1...v2.67.2) (2024-12-16)


### Bug Fixes

* `isBrowser()` to include check on `window` ([#982](https://github.com/supabase/auth-js/issues/982)) ([645f224](https://github.com/supabase/auth-js/commit/645f22447e68ba13e43e359d1524e95fe025d771))

## [2.67.1](https://github.com/supabase/auth-js/compare/v2.67.0...v2.67.1) (2024-12-13)


### Bug Fixes

* 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))

## [2.67.0](https://github.com/supabase/auth-js/compare/v2.66.1...v2.67.0) (2024-12-12)


### Features

* 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)


### Bug Fixes

* add email_address_invalid error code ([#994](https://github.com/supabase/auth-js/issues/994)) ([232f133](https://github.com/supabase/auth-js/commit/232f133b1a84b4c667e994f472098aa5cde2088d))
* return error early for redirects ([#992](https://github.com/supabase/auth-js/issues/992)) ([9f32d30](https://github.com/supabase/auth-js/commit/9f32d30e17954c5d4320b374a108617cda5ab357))

## [2.66.1](https://github.com/supabase/auth-js/compare/v2.66.0...v2.66.1) (2024-12-04)


### Bug Fixes

* 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))
* add new error codes ([#979](https://github.com/supabase/auth-js/issues/979)) ([dfb40d2](https://github.com/supabase/auth-js/commit/dfb40d24188f7e8b0d34e51ded15582086250c51))
* 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))

## [2.66.0](https://github.com/supabase/auth-js/compare/v2.65.1...v2.66.0) (2024-11-01)


Expand Down
12 changes: 6 additions & 6 deletions example/react/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"main": "dist/main/index.js",
"module": "dist/module/index.js",
"types": "dist/module/index.d.ts",
"repository": "supabase/auth-js",
"repository": "github:supabase/auth-js",
"scripts": {
"clean": "rimraf dist docs",
"coverage": "echo \"run npm test\"",
Expand All @@ -30,7 +30,7 @@
"build:module": "tsc -p tsconfig.module.json",
"lint": "eslint ./src/**/* test/**/*.test.ts",
"test": "run-s test:clean test:infra test:suite test:clean",
"test:suite": "jest --runInBand",
"test:suite": "jest --runInBand --coverage",
"test:infra": "cd infra && docker compose down && docker compose pull && docker compose up -d && sleep 30",
"test:clean": "cd infra && docker compose down",
"docs": "typedoc src/index.ts --out docs/v2 --excludePrivate --excludeProtected",
Expand Down
98 changes: 64 additions & 34 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
isAuthError,
isAuthRetryableFetchError,
isAuthSessionMissingError,
isAuthImplicitGrantRedirectError,
} from './lib/errors'
import {
Fetch,
Expand Down Expand Up @@ -88,13 +89,11 @@ import type {
LockFunc,
UserIdentity,
SignInAnonymouslyCredentials,
} from './lib/types'
import {
MFAEnrollTOTPParams,
MFAEnrollPhoneParams,
AuthMFAEnrollTOTPResponse,
AuthMFAEnrollPhoneResponse,
} from './lib/internal-types'
} from './lib/types'

polyfillGlobalThis() // Make "globalThis" available

Expand Down Expand Up @@ -306,16 +305,34 @@ export default class GoTrueClient {
*/
private async _initialize(): Promise<InitializeResult> {
try {
const isPKCEFlow = isBrowser() ? await this._isPKCEFlow() : false
this._debug('#_initialize()', 'begin', 'is PKCE flow', isPKCEFlow)

if (isPKCEFlow || (this.detectSessionInUrl && this._isImplicitGrantFlow())) {
const { data, error } = await this._getSessionFromURL(isPKCEFlow)
const params = parseParametersFromURL(window.location.href)
let callbackUrlType = 'none'
if (this._isImplicitGrantCallback(params)) {
callbackUrlType = 'implicit'
} else if (await this._isPKCECallback(params)) {
callbackUrlType = 'pkce'
}

/**
* Attempt to get the session from the URL only if these conditions are fulfilled
*
* Note: If the URL isn't one of the callback url types (implicit or pkce),
* then there could be an existing session so we don't want to prematurely remove it
*/
if (isBrowser() && this.detectSessionInUrl && callbackUrlType !== 'none') {
const { data, error } = await this._getSessionFromURL(params, callbackUrlType)
if (error) {
this._debug('#_initialize()', 'error detecting session from URL', error)

if (error?.code === 'identity_already_exists') {
return { error }
if (isAuthImplicitGrantRedirectError(error)) {
const errorCode = error.details?.code
if (
errorCode === 'identity_already_exists' ||
errorCode === 'identity_not_found' ||
errorCode === 'single_identity_not_deletable'
) {
return { error }
}
}

// failed login attempt via url,
Expand Down Expand Up @@ -1419,7 +1436,10 @@ export default class GoTrueClient {
/**
* Gets the session data from a URL string
*/
private async _getSessionFromURL(isPKCEFlow: boolean): Promise<
private async _getSessionFromURL(
params: { [parameter: string]: string },
callbackUrlType: string
): Promise<
| {
data: { session: Session; redirectType: string | null }
error: null
Expand All @@ -1428,15 +1448,39 @@ export default class GoTrueClient {
> {
try {
if (!isBrowser()) throw new AuthImplicitGrantRedirectError('No browser detected.')
if (this.flowType === 'implicit' && !this._isImplicitGrantFlow()) {
throw new AuthImplicitGrantRedirectError('Not a valid implicit grant flow url.')
} else if (this.flowType == 'pkce' && !isPKCEFlow) {
throw new AuthPKCEGrantCodeExchangeError('Not a valid PKCE flow url.')

// If there's an error in the URL, it doesn't matter what flow it is, we just return the error.
if (params.error || params.error_description || params.error_code) {
// The error class returned implies that the redirect is from an implicit grant flow
// but it could also be from a redirect error from a PKCE flow.
throw new AuthImplicitGrantRedirectError(
params.error_description || 'Error in URL with unspecified error_description',
{
error: params.error || 'unspecified_error',
code: params.error_code || 'unspecified_code',
}
)
}

const params = parseParametersFromURL(window.location.href)
// Checks for mismatches between the flowType initialised in the client and the URL parameters
switch (callbackUrlType) {
case 'implicit':
if (this.flowType === 'pkce') {
throw new AuthPKCEGrantCodeExchangeError('Not a valid PKCE flow url.')
}
break
case 'pkce':
if (this.flowType === 'implicit') {
throw new AuthImplicitGrantRedirectError('Not a valid implicit grant flow url.')
}
break
default:
// there's no mismatch so we continue
}

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

if (params.error || params.error_description || params.error_code) {
throw new AuthImplicitGrantRedirectError(
params.error_description || 'Error in URL with unspecified error_description',
{
error: params.error || 'unspecified_error',
code: params.error_code || 'unspecified_code',
}
)
}

const {
provider_token,
provider_refresh_token,
Expand Down Expand Up @@ -1536,18 +1570,14 @@ export default class GoTrueClient {
/**
* 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)
*/
private _isImplicitGrantFlow(): boolean {
const params = parseParametersFromURL(window.location.href)

return !!(isBrowser() && (params.access_token || params.error_description))
private _isImplicitGrantCallback(params: { [parameter: string]: string }): boolean {
return Boolean(params.access_token || params.error_description)
}

/**
* Checks if the current URL and backing storage contain parameters given by a PKCE flow
*/
private async _isPKCEFlow(): Promise<boolean> {
const params = parseParametersFromURL(window.location.href)

private async _isPKCECallback(params: { [parameter: string]: string }): Promise<boolean> {
const currentStorageContent = await getItemAsync(
this.storage,
`${this.storageKey}-code-verifier`
Expand Down
1 change: 1 addition & 0 deletions src/lib/error-codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,4 @@ export type ErrorCode =
| 'mfa_verified_factor_exists'
| 'invalid_credentials'
| 'email_address_not_authorized'
| 'email_address_invalid'
6 changes: 6 additions & 0 deletions src/lib/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,12 @@ export class AuthImplicitGrantRedirectError extends CustomAuthError {
}
}

export function isAuthImplicitGrantRedirectError(
error: any
): error is AuthImplicitGrantRedirectError {
return isAuthError(error) && error.name === 'AuthImplicitGrantRedirectError'
}

export class AuthPKCEGrantCodeExchangeError extends CustomAuthError {
details: { error: string; code: string } | null = null

Expand Down
2 changes: 1 addition & 1 deletion src/lib/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function uuid() {
})
}

export const isBrowser = () => typeof document !== 'undefined'
export const isBrowser = () => typeof window !== 'undefined' && typeof document !== 'undefined'

const localStorageWriteTests = {
tested: false,
Expand Down
Loading

0 comments on commit dde5d7f

Please sign in to comment.