Skip to content

Devin/1744245477 implement login with popup v2 #879

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 36 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
4b31d51
Implement loginWithPopupV2 method in React Native OAuth extensions
devin-ai-integration[bot] Apr 10, 2025
bfd8260
Fix loginWithPopupV2 implementation to use Verify method
devin-ai-integration[bot] Apr 10, 2025
6a936fa
Merge remote-tracking branch 'origin/master' into devin/1744245477-im…
Ethella Apr 10, 2025
f32e4dd
Adding babel plugins
Ethella Apr 10, 2025
1e25da4
bump compiler options to es2022
Ethella Apr 11, 2025
16ecf0c
bump esbuild target to es2022
Ethella Apr 11, 2025
9a94fab
remove local forage
Ethella Apr 22, 2025
86d0520
bump expo-web-browser
Ethella Apr 22, 2025
2ac647e
remove unused dependency
Ethella Apr 22, 2025
fd87f2e
add back rn
Ethella Apr 22, 2025
7593a93
mock localforage
Ethella Apr 22, 2025
ab6aafb
revert build target
Ethella Apr 22, 2025
b3835d2
es2022
Ethella Apr 22, 2025
45f6d0a
Remove Extension class and rename InternalExtension to MagicExtension
devin-ai-integration[bot] Apr 22, 2025
7e48844
remove static field
Ethella Apr 22, 2025
ab7c4b6
remove static field
Ethella Apr 22, 2025
3926fca
Merge remote-tracking branch 'origin/master' into devin/1745362241-re…
Ethella Apr 22, 2025
560bf53
Merge branch 'devin/1745362241-remove-extension-class' into devin/174…
Ethella Apr 22, 2025
7e2c130
update yarn.lock
Ethella Apr 22, 2025
abd9d4e
fix test
Ethella Apr 22, 2025
f976d17
fix tests
Ethella Apr 22, 2025
4f06492
Merge branch 'devin/1745362241-remove-extension-class' into devin/174…
Ethella Apr 22, 2025
641c2b1
Merge remote-tracking branch 'origin/master' into devin/1744245477-im…
Ethella Apr 23, 2025
89580d3
update yarn.lock
Ethella Apr 23, 2025
a30deaa
Revert "Remove Extension class and rename InternalExtension to MagicE…
Ethella Apr 23, 2025
9b97350
Merge remote-tracking branch 'origin/revert-881-devin/1745362241-remo…
Ethella Apr 23, 2025
458f0bc
bump target to es 2022
Ethella Apr 23, 2025
4ce06d4
Merge branch 'master' into devin/1744245477-implement-login-with-popu…
Ethella Apr 24, 2025
d05d397
Merge branch 'master' into devin/1744245477-implement-login-with-popu…
Ethella May 1, 2025
193193c
update yarn.lock
Ethella May 1, 2025
9c736c2
Merge remote-tracking branch 'origin/master' into devin/1744245477-im…
Ethella May 1, 2025
a18a0cd
update yarn.lock
Ethella May 1, 2025
8dc1806
remove static plugin
Ethella May 1, 2025
4467412
remove crypto
Ethella May 1, 2025
607f7bc
Merge remote-tracking branch 'origin/master' into devin/1744245477-im…
Ethella Jun 10, 2025
9dcb8da
update yarn.lock
Ethella Jun 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/@magic-ext/react-native-bare-oauth/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['module:@react-native/babel-preset'],
};
7 changes: 2 additions & 5 deletions packages/@magic-ext/react-native-bare-oauth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,11 @@
]
},
"dependencies": {
"crypto-js": "^4.2.0",
"react-native-device-info": "^10.3.0"
"react-native-inappbrowser-reborn": "^3.7.0"
},
"devDependencies": {
"@magic-sdk/react-native-bare": "^30.1.0",
"@magic-sdk/types": "^24.19.0",
"@types/crypto-js": "~4.2.0",
"react-native-inappbrowser-reborn": "^3.7.0"
"@magic-sdk/types": "^24.19.0"
},
"peerDependencies": {
"@magic-sdk/react-native-bare": ">=13.0.0",
Expand Down
79 changes: 0 additions & 79 deletions packages/@magic-ext/react-native-bare-oauth/src/crypto.ts

This file was deleted.

103 changes: 35 additions & 68 deletions packages/@magic-ext/react-native-bare-oauth/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import { InAppBrowser } from 'react-native-inappbrowser-reborn';
import { Extension } from '@magic-sdk/react-native-bare';
import { getBundleId } from 'react-native-device-info';
import { createCryptoChallenge } from './crypto';
import {
OAuthErrorData,
OAuthPayloadMethods,
OAuthRedirectConfiguration,
OAuthRedirectError,
OAuthRedirectResult,
OAuthRedirectStartResult,
} from './types';

export class OAuthExtension extends Extension.Internal<'oauth'> {
Expand All @@ -23,18 +22,38 @@ export class OAuthExtension extends Extension.Internal<'oauth'> {
public loginWithPopup(configuration: OAuthRedirectConfiguration) {
return this.utils.createPromiEvent<OAuthRedirectResult>(async (resolve, reject) => {
try {
const { provider, query, redirectURI } = await createURI.call(this, configuration);
const url = `https://auth.magic.link/v1/oauth2/${provider}/start?${query}`;
const startPayload = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Start, [
{
...configuration,
apiKey: this.sdk.apiKey,
platform: 'rn',
},
]);

const result = await this.request<OAuthRedirectStartResult | OAuthRedirectError>(startPayload);
const successResult = result as OAuthRedirectStartResult;
const errorResult = result as OAuthRedirectError;

if (errorResult.error) {
reject(
this.createError<OAuthErrorData>(errorResult.error, errorResult.error_description ?? 'An error occurred.', {
errorURI: errorResult.error_uri,
provider: errorResult.provider,
}),
);
return;
}

if (!successResult?.oauthAuthoriationURI) {
reject(this.createError<object>('NO_AUTH_URI', 'No authorization URI was returned', {}));
return;
}

/**
* Response Type Inspired by:
* https://docs.expo.io/versions/latest/sdk/webbrowser/#returns
*/
const res = await InAppBrowser.openAuth(url, redirectURI, {});
const url = successResult.oauthAuthoriationURI;
const res = await InAppBrowser.openAuth(url, configuration.redirectURI, {});

if (res.type === 'success') {
const queryString = new URL(res.url).search;

resolve(getResult.call(this, queryString.toString()));
} else {
reject(this.createError<object>(res.type, 'User has cancelled the authentication', {}));
Expand All @@ -50,66 +69,14 @@ export class OAuthExtension extends Extension.Internal<'oauth'> {
}
}

const OAUTH_REDIRECT_METADATA_KEY = 'oauth_redirect_metadata';

export async function createURI(this: OAuthExtension, configuration: OAuthRedirectConfiguration) {
// Bust any old, in-progress OAuth flows.
await this.utils.storage.removeItem(OAUTH_REDIRECT_METADATA_KEY);

// Unpack configuration, generate crypto values, and persist to storage.
const { provider, redirectURI, scope, loginHint } = configuration;
const { verifier, challenge, state } = await createCryptoChallenge();
const bundleId = getBundleId();

/* Stringify for RN Async storage */
const storedData = JSON.stringify({
verifier,
state,
});

await this.utils.storage.setItem(OAUTH_REDIRECT_METADATA_KEY, storedData);

// Formulate the initial redirect query to Magic's OAuth hub.
// Required fields:
// - `magic_api_key`
// - `magic_challenge`
// - `state`
// - `redirect_uri`
// - `platform`
// Optional fields:
// - `bundleId`

const query = [
`magic_api_key=${encodeURIComponent(this.sdk.apiKey)}`,
`magic_challenge=${encodeURIComponent(challenge)}`,
`state=${encodeURIComponent(state)}`,
`platform=${encodeURIComponent('rn')}`,
scope && `scope=${encodeURIComponent(scope.join(' '))}`,
redirectURI && `redirect_uri=${encodeURIComponent(redirectURI)}`,
loginHint && `login_hint=${encodeURIComponent(loginHint)}`,
bundleId && `bundleId=${encodeURIComponent(bundleId)}`,
].reduce((prev, next) => (next ? `${prev}&${next}` : prev));

return {
query,
provider,
redirectURI,
};
}

export function getResult(this: OAuthExtension, queryString: string) {
return this.utils.createPromiEvent<OAuthRedirectResult>(async (resolve, reject) => {
const json: string = (await this.utils.storage.getItem(OAUTH_REDIRECT_METADATA_KEY)) as string;

const { verifier, state } = JSON.parse(json);

// Remove the save OAuth state from storage, it stays in memory now...
this.utils.storage.removeItem(OAUTH_REDIRECT_METADATA_KEY);

const parseRedirectResult = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.ParseRedirectResult, [
queryString,
verifier,
state,
const parseRedirectResult = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [
{
authorizationResponseParams: queryString,
magicApiKey: this.sdk.apiKey,
platform: 'rn',
},
]);

// Parse the result, which may contain an OAuth-formatted error.
Expand Down
10 changes: 9 additions & 1 deletion packages/@magic-ext/react-native-bare-oauth/src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { MagicUserMetadata } from '@magic-sdk/types';

export enum OAuthPayloadMethods {
ParseRedirectResult = 'magic_oauth_parse_redirect_result',
Start = 'magic_oauth_login_with_redirect_start',
Verify = 'magic_oauth_login_with_redirect_verify',
Popup = 'magic_oauth_login_with_popup',
}

export type OAuthProvider =
Expand Down Expand Up @@ -107,3 +109,9 @@ export enum OAuthErrorCode {
ServerError = 'server_error',
TemporarilyUnavailable = 'temporarily_unavailable',
}

export interface OAuthRedirectStartResult {
oauthAuthoriationURI?: string;
useMagicServerCallback?: boolean;
shouldReturnURI?: boolean;
}
3 changes: 3 additions & 0 deletions packages/@magic-ext/react-native-expo-oauth/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
presets: ['babel-preset-expo'],
};
11 changes: 6 additions & 5 deletions packages/@magic-ext/react-native-expo-oauth/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,17 @@
]
},
"dependencies": {
"@magic-sdk/types": "^10.0.0",
"crypto-js": "^4.2.0",
"expo-application": "^5.0.1",
"expo-web-browser": ">=12.0.0"
"expo-web-browser": "14.0.2"
},
"devDependencies": {
"@magic-sdk/react-native-expo": "^30.1.0",
"@magic-sdk/types": "^24.18.2",
"@react-native-async-storage/async-storage": "^2.1.2",
"@types/crypto-js": "~4.2.0"
},
"peerDependencies": {
"@magic-sdk/react-native-expo": ">=13.0.0"
"@magic-sdk/react-native-expo": ">=13.0.0",
"@magic-sdk/types": "^24.18.2",
"@react-native-async-storage/async-storage": "^2.1.2"
}
}
79 changes: 0 additions & 79 deletions packages/@magic-ext/react-native-expo-oauth/src/crypto.ts

This file was deleted.

Loading