Skip to content

Commit

Permalink
Merge pull request #771 from magiclabs/PDEEXP-1658-update-login-sdk-m…
Browse files Browse the repository at this point in the history
…ethod-to-handle-DID-lifespan

add lifespan parameter to login methods
  • Loading branch information
romin-halltari authored Aug 2, 2024
2 parents eed7c58 + 49eb7fc commit 041dab7
Show file tree
Hide file tree
Showing 11 changed files with 66 additions and 28 deletions.
10 changes: 6 additions & 4 deletions packages/@magic-ext/oauth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,15 @@ export class OAuthExtension extends Extension.Internal<'oauth'> {
});
}

public getRedirectResult() {
public getRedirectResult(lifespan?: number) {
const queryString = window.location.search;

// Remove the query from the redirect callback as a precaution to prevent
// malicious parties from parsing it before we have a chance to use it.
const urlWithoutQuery = window.location.origin + window.location.pathname;
window.history.replaceState(null, '', urlWithoutQuery);

return getResult.call(this, queryString);
return getResult.call(this, queryString, lifespan);
}
}

Expand All @@ -50,7 +50,7 @@ async function createURI(this: OAuthExtension, configuration: OAuthRedirectConfi
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 { provider, redirectURI, scope, loginHint, lifespan } = configuration;
const { verifier, challenge, state } = await createCryptoChallenge();

/* Stringify for RN Async storage */
Expand All @@ -77,6 +77,7 @@ async function createURI(this: OAuthExtension, configuration: OAuthRedirectConfi
scope && `scope=${encodeURIComponent(scope.join(' '))}`,
redirectURI && `redirect_uri=${encodeURIComponent(redirectURI)}`,
loginHint && `login_hint=${encodeURIComponent(loginHint)}`,
lifespan && `lifespan=${encodeURIComponent(lifespan)}`,
].reduce((prev, next) => (next ? `${prev}&${next}` : prev));

return {
Expand All @@ -86,7 +87,7 @@ async function createURI(this: OAuthExtension, configuration: OAuthRedirectConfi
};
}

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

Expand All @@ -99,6 +100,7 @@ function getResult(this: OAuthExtension, queryString: string) {
queryString,
verifier,
state,
lifespan,
]);

// Parse the result, which may contain an OAuth-formatted error.
Expand Down
1 change: 1 addition & 0 deletions packages/@magic-ext/oauth/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface OAuthRedirectConfiguration {
redirectURI: string;
scope?: string[];
loginHint?: string;
lifespan?: number;
}

export enum OAuthErrorCode {
Expand Down
7 changes: 4 additions & 3 deletions packages/@magic-ext/oauth2/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,25 +54,26 @@ export class OAuthExtension extends Extension.Internal<'oauth2'> {
});
}

public getRedirectResult() {
public getRedirectResult(lifespan?: number) {
const queryString = window.location.search;

// Remove the query from the redirect callback as a precaution to prevent
// malicious parties from parsing it before we have a chance to use it.
const urlWithoutQuery = window.location.origin + window.location.pathname;
window.history.replaceState(null, '', urlWithoutQuery);

return getResult.call(this, queryString);
return getResult.call(this, queryString, lifespan);
}
}

function getResult(this: OAuthExtension, queryString: string) {
function getResult(this: OAuthExtension, queryString: string, lifespan?: number) {
return this.utils.createPromiEvent<OAuthRedirectResult>(async (resolve, reject) => {
const parseRedirectResult = this.utils.createJsonRpcRequestPayload(OAuthPayloadMethods.Verify, [
{
authorizationResponseParams: queryString,
magicApiKey: this.sdk.apiKey,
platform: 'web',
lifespan,
},
]);

Expand Down
1 change: 1 addition & 0 deletions packages/@magic-ext/oauth2/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ export interface OAuthRedirectConfiguration {
redirectURI: string;
scope?: string[];
loginHint?: string;
lifespan?: string;
}

export enum OAuthErrorCode {
Expand Down
3 changes: 2 additions & 1 deletion packages/@magic-ext/react-native-bare-oauth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function createURI(this: OAuthExtension, configuration: OAuthRedire
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 { provider, redirectURI, scope, loginHint, lifespan } = configuration;
const { verifier, challenge, state } = await createCryptoChallenge();
const bundleId = getBundleId();

Expand Down Expand Up @@ -88,6 +88,7 @@ export async function createURI(this: OAuthExtension, configuration: OAuthRedire
redirectURI && `redirect_uri=${encodeURIComponent(redirectURI)}`,
loginHint && `login_hint=${encodeURIComponent(loginHint)}`,
bundleId && `bundleId=${encodeURIComponent(bundleId)}`,
lifespan && `lifespan=${encodeURIComponent(lifespan)}`,
].reduce((prev, next) => (next ? `${prev}&${next}` : prev));

return {
Expand Down
1 change: 1 addition & 0 deletions packages/@magic-ext/react-native-bare-oauth/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface OAuthRedirectConfiguration {
redirectURI: string;
scope?: string[];
loginHint?: string;
lifespan?: string;
}

export enum OAuthErrorCode {
Expand Down
3 changes: 2 additions & 1 deletion packages/@magic-ext/react-native-expo-oauth/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function createURI(this: OAuthExtension, configuration: OAuthRedire
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 { provider, redirectURI, scope, loginHint, lifespan } = configuration;
const { verifier, challenge, state } = await createCryptoChallenge();
const bundleId = Application.applicationId;

Expand Down Expand Up @@ -88,6 +88,7 @@ export async function createURI(this: OAuthExtension, configuration: OAuthRedire
redirectURI && `redirect_uri=${encodeURIComponent(redirectURI)}`,
loginHint && `login_hint=${encodeURIComponent(loginHint)}`,
bundleId && `bundleId=${encodeURIComponent(bundleId)}`,
lifespan && `lifespan=${encodeURIComponent(lifespan)}`,
].reduce((prev, next) => (next ? `${prev}&${next}` : prev));

return {
Expand Down
1 change: 1 addition & 0 deletions packages/@magic-ext/react-native-expo-oauth/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ export interface OAuthRedirectConfiguration {
redirectURI: string;
scope?: string[];
loginHint?: string;
lifespan?: string;
}

export enum OAuthErrorCode {
Expand Down
18 changes: 10 additions & 8 deletions packages/@magic-sdk/provider/src/modules/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
UpdateEmailEventHandlers,
UpdateEmailEventEmit,
RecencyCheckEventEmit,
LoginWithCredentialConfiguration,
} from '@magic-sdk/types';
import { BaseModule } from './base-module';
import { createJsonRpcRequestPayload } from '../core/json-rpc';
Expand Down Expand Up @@ -51,11 +52,11 @@ export class AuthModule extends BaseModule {
}).log();
}

const { email, showUI = true, redirectURI, overrides } = configuration;
const { email, showUI = true, redirectURI, overrides, lifespan } = configuration;

const requestPayload = createJsonRpcRequestPayload(
this.sdk.testMode ? MagicPayloadMethod.LoginWithMagicLinkTestMode : MagicPayloadMethod.LoginWithMagicLink,
[{ email, showUI, redirectURI, overrides }],
[{ email, showUI, redirectURI, overrides, lifespan }],
);
return this.request<string | null, LoginWithMagicLinkEventHandlers>(requestPayload);
}
Expand All @@ -66,10 +67,10 @@ export class AuthModule extends BaseModule {
* of 15 minutes)
*/
public loginWithSMS(configuration: LoginWithSmsConfiguration) {
const { phoneNumber } = configuration;
const { phoneNumber, lifespan } = configuration;
const requestPayload = createJsonRpcRequestPayload(
this.sdk.testMode ? MagicPayloadMethod.LoginWithSmsTestMode : MagicPayloadMethod.LoginWithSms,
[{ phoneNumber, showUI: true }],
[{ phoneNumber, showUI: true, lifespan }],
);
return this.request<string | null>(requestPayload);
}
Expand All @@ -80,10 +81,10 @@ export class AuthModule extends BaseModule {
* of 15 minutes)
*/
public loginWithEmailOTP(configuration: LoginWithEmailOTPConfiguration) {
const { email, showUI, deviceCheckUI, overrides } = configuration;
const { email, showUI, deviceCheckUI, overrides, lifespan } = configuration;
const requestPayload = createJsonRpcRequestPayload(
this.sdk.testMode ? MagicPayloadMethod.LoginWithEmailOTPTestMode : MagicPayloadMethod.LoginWithEmailOTP,
[{ email, showUI, deviceCheckUI, overrides }],
[{ email, showUI, deviceCheckUI, overrides, lifespan }],
);
const handle = this.request<string | null, LoginWithEmailOTPEventHandlers>(requestPayload);
if (!deviceCheckUI && handle) {
Expand Down Expand Up @@ -112,7 +113,8 @@ export class AuthModule extends BaseModule {
* If no argument is provided, a credential is automatically parsed from
* `window.location.search`.
*/
public loginWithCredential(credentialOrQueryString?: string) {
public loginWithCredential(configuration?: LoginWithCredentialConfiguration) {
const { credentialOrQueryString, lifespan } = configuration || {};
let credentialResolved = credentialOrQueryString ?? '';

if (!credentialOrQueryString && SDKEnvironment.platform === 'web') {
Expand All @@ -125,7 +127,7 @@ export class AuthModule extends BaseModule {

const requestPayload = createJsonRpcRequestPayload(
this.sdk.testMode ? MagicPayloadMethod.LoginWithCredentialTestMode : MagicPayloadMethod.LoginWithCredential,
[credentialResolved],
[credentialResolved, lifespan],
);

return this.request<string | null>(requestPayload);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,19 @@ beforeEach(() => {
jest.restoreAllMocks();
});

test('Generates JSON RPC request payload with the given parameter as the credential', async () => {
test('Generates JSON RPC request payload with the given parameters as the credential and lifespan', async () => {
const magic = createMagicSDK({ platform: 'web' });
magic.auth.request = jest.fn();

await magic.auth.loginWithCredential('helloworld');
await magic.auth.loginWithCredential({ credentialOrQueryString: 'helloworld', lifespan: 900 });

const requestPayload = magic.auth.request.mock.calls[0][0];
expect(requestPayload.jsonrpc).toBe('2.0');
expect(requestPayload.method).toBe(MagicPayloadMethod.LoginWithCredential);
expect(requestPayload.params).toEqual(['helloworld']);
expect(requestPayload.params).toEqual(['helloworld', 900]);
});

test('If no parameter is given & platform target is "web", URL search string is included in the payload params', async () => {
test('If no parameters are given & platform target is "web", URL search string and default lifespan are included in the payload params', async () => {
const magic = createMagicSDK({ platform: 'web' });
magic.auth.request = jest.fn();

Expand All @@ -40,10 +40,10 @@ test('If no parameter is given & platform target is "web", URL search string is
const requestPayload = magic.auth.request.mock.calls[0][0];
expect(requestPayload.jsonrpc).toBe('2.0');
expect(requestPayload.method).toBe(MagicPayloadMethod.LoginWithCredential);
expect(requestPayload.params).toEqual(['?magic_credential=asdf']);
expect(requestPayload.params).toEqual(['?magic_credential=asdf', undefined]);
});

test('If no parameter is given & platform target is NOT "web", credential is empty string', async () => {
test('If no parameters are given & platform target is NOT "web", credential is empty string and default lifespan is included', async () => {
const magic = createMagicSDK({ platform: 'react-native' });
magic.auth.request = jest.fn();

Expand All @@ -52,22 +52,22 @@ test('If no parameter is given & platform target is NOT "web", credential is emp
const requestPayload = magic.auth.request.mock.calls[0][0];
expect(requestPayload.jsonrpc).toBe('2.0');
expect(requestPayload.method).toBe(MagicPayloadMethod.LoginWithCredential);
expect(requestPayload.params).toEqual(['']);
expect(requestPayload.params).toEqual(['', undefined]);
});

test('If `testMode` is enabled, testing-specific RPC method is used', async () => {
test('If `testMode` is enabled, testing-specific RPC method is used with given parameters', async () => {
const magic = createMagicSDKTestMode({ platform: 'web' });
magic.auth.request = jest.fn();

await magic.auth.loginWithCredential('helloworld');
await magic.auth.loginWithCredential({ credentialOrQueryString: 'helloworld', lifespan: 900 });

const requestPayload = magic.auth.request.mock.calls[0][0];
expect(requestPayload.jsonrpc).toBe('2.0');
expect(requestPayload.method).toBe(MagicPayloadMethod.LoginWithCredentialTestMode);
expect(requestPayload.params).toEqual(['helloworld']);
expect(requestPayload.params).toEqual(['helloworld', 900]);
});

test('method should return a PromiEvent', () => {
const magic = createMagicSDK({ platform: 'web' });
expect(isPromiEvent(magic.auth.loginWithCredential('asdf'))).toBeTruthy();
expect(isPromiEvent(magic.auth.loginWithCredential({ credentialOrQueryString: 'asdf' }))).toBeTruthy();
});
27 changes: 27 additions & 0 deletions packages/@magic-sdk/types/src/modules/auth-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,23 @@ export interface LoginWithMagicLinkConfiguration {
overrides?: {
variation?: string;
};

/**
* The number of seconds until the generated Decenteralized ID token will expire.
*/
lifespan?: number;
}

export interface LoginWithSmsConfiguration {
/**
* Specify the phone number of the user attempting to login.
*/
phoneNumber: string;

/**
* The number of seconds until the generated Decenteralized ID token will expire.
*/
lifespan?: number;
}
export interface LoginWithEmailOTPConfiguration {
/**
Expand Down Expand Up @@ -74,6 +84,23 @@ export interface LoginWithEmailOTPConfiguration {
overrides?: {
variation?: string;
};

/**
* The number of seconds until the generated Decenteralized ID token will expire.
*/
lifespan?: number;
}

export interface LoginWithCredentialConfiguration {
/**
* A credential token or a valid query string (prefixed with ? or #)
*/
credentialOrQueryString?: string;

/**
* The number of seconds until the generated Decenteralized ID token will expire.
*/
lifespan?: number;
}

/**
Expand Down

0 comments on commit 041dab7

Please sign in to comment.