Skip to content

Commit

Permalink
Merge branch 'master' into dependabot/npm_and_yarn/example/react/nano…
Browse files Browse the repository at this point in the history
…id-3.3.8
  • Loading branch information
staaldraad authored Jan 22, 2025
2 parents 2c70846 + 80f88e4 commit 779c355
Show file tree
Hide file tree
Showing 11 changed files with 242 additions and 138 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
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# 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)


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
104 changes: 62 additions & 42 deletions src/GoTrueClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import GoTrueAdminApi from './GoTrueAdminApi'
import { DEFAULT_HEADERS, EXPIRY_MARGIN, GOTRUE_URL, STORAGE_KEY } from './lib/constants'
import {
DEFAULT_HEADERS,
EXPIRY_MARGIN_MS,
AUTO_REFRESH_TICK_DURATION_MS,
AUTO_REFRESH_TICK_THRESHOLD,
GOTRUE_URL,
STORAGE_KEY,
} from './lib/constants'
import {
AuthError,
AuthImplicitGrantRedirectError,
Expand Down Expand Up @@ -89,13 +96,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 All @@ -111,13 +116,6 @@ const DEFAULT_OPTIONS: Omit<Required<GoTrueClientOptions>, 'fetch' | 'storage' |
hasCustomAuthorizationHeader: false,
}

/** Current session will be checked for refresh at this interval. */
const AUTO_REFRESH_TICK_DURATION = 30 * 1000

/**
* A token refresh will be attempted this many ticks before the current session expires. */
const AUTO_REFRESH_TICK_THRESHOLD = 3

async function lockNoOp<R>(name: string, acquireTimeout: number, fn: () => Promise<R>): Promise<R> {
return await fn()
}
Expand Down Expand Up @@ -307,8 +305,22 @@ export default class GoTrueClient {
*/
private async _initialize(): Promise<InitializeResult> {
try {
if (isBrowser() && this.detectSessionInUrl) {
const { data, error } = await this._getSessionFromURL()
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)

Expand Down Expand Up @@ -1095,8 +1107,13 @@ export default class GoTrueClient {
return { data: { session: null }, error: null }
}

// A session is considered expired before the access token _actually_
// expires. When the autoRefreshToken option is off (or when the tab is
// in the background), very eager users of getSession() -- like
// realtime-js -- might send a valid JWT which will expire by the time it
// reaches the server.
const hasExpired = currentSession.expires_at
? currentSession.expires_at <= Date.now() / 1000
? currentSession.expires_at * 1000 - Date.now() < EXPIRY_MARGIN_MS
: false

this._debug(
Expand Down Expand Up @@ -1411,7 +1428,10 @@ export default class GoTrueClient {
/**
* Gets the session data from a URL string
*/
private async _getSessionFromURL(): Promise<
private async _getSessionFromURL(
params: { [parameter: string]: string },
callbackUrlType: string
): Promise<
| {
data: { session: Session; redirectType: string | null }
error: null
Expand All @@ -1421,8 +1441,6 @@ export default class GoTrueClient {
try {
if (!isBrowser()) throw new AuthImplicitGrantRedirectError('No browser detected.')

const params = parseParametersFromURL(window.location.href)

// 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
Expand All @@ -1436,23 +1454,25 @@ export default class GoTrueClient {
)
}

const isRedirectFromImplicitGrantFlow = this._isImplicitGrantFlow(params)
const isRedirectFromPKCEFlow = await this._isPKCEFlow(params)

// Checks for mismatches between the flowType initialised in the client and the URL parameters
if (!isRedirectFromImplicitGrantFlow && !isRedirectFromPKCEFlow) {
if (this.flowType === 'implicit') {
throw new AuthImplicitGrantRedirectError('Not a valid implicit grant flow url.')
} else if (this.flowType === 'pkce') {
throw new AuthPKCEGrantCodeExchangeError('Not a valid PKCE flow url.')
} else {
throw new AuthError('Invalid flow type.')
}
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
}

// Since this is a redirect for PKCE, we attempt to retrieve the code from the URL for the code exchange
if (isRedirectFromPKCEFlow) {
this._debug('#_initialize()', 'begin', 'is PKCE flow', isRedirectFromPKCEFlow)
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 Down Expand Up @@ -1488,7 +1508,7 @@ export default class GoTrueClient {
}

const actuallyExpiresIn = expiresAt - timeNow
if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION) {
if (actuallyExpiresIn * 1000 <= AUTO_REFRESH_TICK_DURATION_MS) {
console.warn(
`@supabase/gotrue-js: Session as retrieved from URL expires in ${actuallyExpiresIn}s, should have been closer to ${expiresIn}s`
)
Expand Down Expand Up @@ -1542,20 +1562,20 @@ 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(params: { [parameter: string]: string }): boolean {
return !!((params.access_token || params.error_description) && this.flowType === 'implicit')
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(params: { [parameter: string]: string }): Promise<boolean> {
private async _isPKCECallback(params: { [parameter: string]: string }): Promise<boolean> {
const currentStorageContent = await getItemAsync(
this.storage,
`${this.storageKey}-code-verifier`
)

return !!(params.code && currentStorageContent && this.flowType === 'pkce')
return !!(params.code && currentStorageContent)
}

/**
Expand Down Expand Up @@ -1835,7 +1855,7 @@ export default class GoTrueClient {
error &&
isAuthRetryableFetchError(error) &&
// retryable only if the request can be sent before the backoff overflows the tick duration
Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION
Date.now() + nextBackOffInterval - startedAt < AUTO_REFRESH_TICK_DURATION_MS
)
}
)
Expand Down Expand Up @@ -1908,12 +1928,12 @@ export default class GoTrueClient {
return
}

const timeNow = Math.round(Date.now() / 1000)
const expiresWithMargin = (currentSession.expires_at ?? Infinity) < timeNow + EXPIRY_MARGIN
const expiresWithMargin =
(currentSession.expires_at ?? Infinity) * 1000 - Date.now() < EXPIRY_MARGIN_MS

this._debug(
debugName,
`session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN}s`
`session has${expiresWithMargin ? '' : ' not'} expired with margin of ${EXPIRY_MARGIN_MS}s`
)

if (expiresWithMargin) {
Expand Down Expand Up @@ -2086,7 +2106,7 @@ export default class GoTrueClient {

this._debug('#_startAutoRefresh()')

const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION)
const ticker = setInterval(() => this._autoRefreshTokenTick(), AUTO_REFRESH_TICK_DURATION_MS)
this.autoRefreshTicker = ticker

if (ticker && typeof ticker === 'object' && typeof ticker.unref === 'function') {
Expand Down Expand Up @@ -2193,12 +2213,12 @@ export default class GoTrueClient {

// session will expire in this many ticks (or has already expired if <= 0)
const expiresInTicks = Math.floor(
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION
(session.expires_at * 1000 - now) / AUTO_REFRESH_TICK_DURATION_MS
)

this._debug(
'#_autoRefreshTokenTick()',
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
`access token expires in ${expiresInTicks} ticks, a tick lasts ${AUTO_REFRESH_TICK_DURATION_MS}ms, refresh threshold is ${AUTO_REFRESH_TICK_THRESHOLD} ticks`
)

if (expiresInTicks <= AUTO_REFRESH_TICK_THRESHOLD) {
Expand Down
Loading

0 comments on commit 779c355

Please sign in to comment.