Skip to content
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

Feature/lit 856 add sych support #186

Merged
merged 11 commits into from
Jul 14, 2023
5 changes: 4 additions & 1 deletion lit.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@
"HEX_TEST_MEMO": "0x4a532d53444b2054657374",

"PKP_PUBKEY_2": "04a9c9a5db5472e6fac05ec001b804d3daa340a9b791e75dd52180312c7f0d4e806150473da6c785fadd8050ced5aada250146a97d928d0deb8b584bc7f169f10a",
"PKP_ETH_ADDRESS_2": "0xf26Bdd71BACf9D99F5739B4b1a2733E209248170"
"PKP_ETH_ADDRESS_2": "0xf26Bdd71BACf9D99F5739B4b1a2733E209248170",
"STYTCH_APP_ID": "project-test-de4e2690-1506-4cf5-8bce-44571ddaebc9",
"STYTCH_USER_ID": "user-test-68103e01-7468-4abf-83c8-885db2ca1c6c",
"STYTCH_TEST_TOKEN": "eyJhbGciOiJSUzI1NiIsImtpZCI6Imp3ay10ZXN0LWZiMjhlYmY2LTQ3NTMtNDdkMS1iMGUzLTRhY2NkMWE1MTc1NyIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsicHJvamVjdC10ZXN0LWRlNGUyNjkwLTE1MDYtNGNmNS04YmNlLTQ0NTcxZGRhZWJjOSJdLCJleHAiOjE2ODg1Njc0MTQsImh0dHBzOi8vc3R5dGNoLmNvbS9zZXNzaW9uIjp7ImlkIjoic2Vzc2lvbi10ZXN0LTlkZDI3ZGE1LTVjNjQtNDE5NS04NjdlLWIxNGE3MWE5M2MxMSIsInN0YXJ0ZWRfYXQiOiIyMDIzLTA3LTA1VDE0OjI1OjE0WiIsImxhc3RfYWNjZXNzZWRfYXQiOiIyMDIzLTA3LTA1VDE0OjI1OjE0WiIsImV4cGlyZXNfYXQiOiIyMDIzLTA5LTEzVDAxOjA1OjE0WiIsImF0dHJpYnV0ZXMiOnsidXNlcl9hZ2VudCI6IiIsImlwX2FkZHJlc3MiOiIifSwiYXV0aGVudGljYXRpb25fZmFjdG9ycyI6W3sidHlwZSI6Im90cCIsImRlbGl2ZXJ5X21ldGhvZCI6ImVtYWlsIiwibGFzdF9hdXRoZW50aWNhdGVkX2F0IjoiMjAyMy0wNy0wNVQxNDoyNToxNFoiLCJlbWFpbF9mYWN0b3IiOnsiZW1haWxfaWQiOiJlbWFpbC10ZXN0LTAwMzZmM2YzLTQ0MjQtNDg2My1iYWQ3LTFkNGU3NTM1ZDJiMCIsImVtYWlsX2FkZHJlc3MiOiJqb3NoQGxpdHByb3RvY29sLmNvbSJ9fV19LCJpYXQiOjE2ODg1NjcxMTQsImlzcyI6InN0eXRjaC5jb20vcHJvamVjdC10ZXN0LWRlNGUyNjkwLTE1MDYtNGNmNS04YmNlLTQ0NTcxZGRhZWJjOSIsIm5iZiI6MTY4ODU2NzExNCwic3ViIjoidXNlci10ZXN0LTY4MTAzZTAxLTc0NjgtNGFiZi04M2M4LTg4NWRiMmNhMWM2YyJ9.rZgaunT1UV2pmliZ0V7nYqYtyfdGas4eY6Q6RCzEEBc5y1K66lopUbvvkfNsLJUjSc3vw12NlIX3Q47zm0XEP8AahrJ0QWAC4v9gmZKVYbKiL2JppqnaxtNLZV9Zo1KAiqm9gdqRQSD29222RTC59PI52AOZd4iTv4lSBIPG2J9rUkUwaRI23bGLMQ8XVkTSS7wcd1Ls08Q-VDXuwl8vuoJhssBfNfxFigk7cKHwbbM-o1sh3upEzV-WFgvJrTstPUNbHOBvGnqKDZX6A_45M5zBnHrerifz4-ST771tajiuW2lQXWvocyYlRT8_a0XBsW77UhU-YBTvKVpj3jmH4A"
}
2 changes: 2 additions & 0 deletions packages/constants/src/lib/enums.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export enum AuthMethodType {
GoogleJwt,
OTP,
AppleJwt,
StytchOtp,
}

/**
Expand All @@ -41,5 +42,6 @@ export enum ProviderType {
EthWallet = 'ethwallet',
WebAuthn = 'webauthn',
Otp = 'otp',
StytchOtp = 'stytchOtp',
Apple = 'apple',
}
31 changes: 31 additions & 0 deletions packages/lit-auth-client/src/lib/lit-auth-client.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ global.TextDecoder = TextDecoder;
// @ts-ignore - set global variable for testing
global.jestTesting = true;

import * as LITCONFIG from './../../../../lit.config.json';

import { ProviderType } from '@lit-protocol/constants';
import { LitAuthClient } from './lit-auth-client';
import GoogleProvider from './providers/GoogleProvider';
Expand All @@ -15,6 +17,7 @@ import WebAuthnProvider from './providers/WebAuthnProvider';
import EthWalletProvider from './providers/EthWalletProvider';
import AppleProvider from './providers/AppleProvider';
import { OtpProvider } from './providers/OtpProvider';
import { StytchOtpAuthenticateOptions } from '@lit-protocol/types';

const isClass = (v: unknown) => {
return typeof v === 'function' && /^\s*class\s+/.test(v.toString());
Expand Down Expand Up @@ -125,6 +128,34 @@ describe('getProvider', () => {
});
});

describe('StytchOtpProvider', () => {
let client: LitAuthClient;
let provider: OtpProvider;

beforeEach(() => {
client = new LitAuthClient({
litRelayConfig: { relayApiKey: 'test-api-key' },
});

provider = client.initProvider<StytchOtpAuthenticateOptions>(ProviderType.StytchOtp, {
appId: LITCONFIG.STYTCH_APP_ID,
userId: LITCONFIG.STYTCH_USER_ID,
});
});

it('should parse jwt and resolve session', async () => {
const token: string = LITCONFIG.STYTCH_TEST_TOKEN;
const userId: string = LITCONFIG.STYTCH_USER_ID;
const authMethod = await provider.authenticate<OtpProviderOptions>({
accessToken: token,
userId: userId,
});
expect(authMethod).toBeDefined();

expect(authMethod.accessToken).toEqual(token);
});
});

// describe('GoogleProvider', () => {
// let client: LitAuthClient;
// let provider: GoogleProvider;
Expand Down
26 changes: 17 additions & 9 deletions packages/lit-auth-client/src/lib/lit-auth-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import {
IRelay,
LitAuthClientOptions,
OAuthProviderOptions,
OtpProviderOptions,
StytchOtpProviderOptions,
ProviderOptions,
SignInWithOTPParams,
OtpProviderOptions
} from '@lit-protocol/types';
import { ProviderType } from '@lit-protocol/constants';
import { LitNodeClient } from '@lit-protocol/lit-node-client';
Expand All @@ -15,8 +16,9 @@ import GoogleProvider from './providers/GoogleProvider';
import DiscordProvider from './providers/DiscordProvider';
import EthWalletProvider from './providers/EthWalletProvider';
import WebAuthnProvider from './providers/WebAuthnProvider';
import { OtpProvider } from './providers/OtpProvider';
import { StytchOtpProvider } from './providers/StytchOtpProvider';
import AppleProvider from './providers/AppleProvider';
import { OtpProvider } from './providers/OtpProvider';

/**
* Class that handles authentication through Lit login
Expand All @@ -38,10 +40,9 @@ export class LitAuthClient {
* Map of providers
*/
private providers: Map<string, BaseProvider>;

private litOtpOptions: OtpProviderOptions | undefined;


/**
* Create a LitAuthClient instance
*
Expand All @@ -66,7 +67,6 @@ export class LitAuthClient {
'An API key is required to use the default Lit Relay server. Please provide either an API key or a custom relay server.'
);
}

if (options?.litOtpConfig) {
this.litOtpOptions = options?.litOtpConfig;
}
Expand Down Expand Up @@ -139,15 +139,23 @@ export class LitAuthClient {
...baseParams,
}) as unknown as T;
break;
case `otp`:
provider = new OtpProvider(
case `stytchOtp`:
provider = new StytchOtpProvider(
{
...baseParams,
...(options as SignInWithOTPParams),
},
this.litOtpOptions
(options as StytchOtpProviderOptions)
) as unknown as T;
break;
case `otp`:
provider = new OtpProvider(
{
...baseParams,
...(options as SignInWithOTPParams),
},
this.litOtpOptions
) as unknown as T;
break;
default:
throw new Error(
"Invalid provider type provided. Only 'google', 'discord', 'ethereum', and 'webauthn' are supported at the moment."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,4 +154,4 @@ export class OtpProvider extends BaseProvider {
return '';
}
}
}
}
104 changes: 104 additions & 0 deletions packages/lit-auth-client/src/lib/providers/StytchOtpProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { AuthMethodType } from '@lit-protocol/constants';
import {
AuthMethod,
BaseAuthenticateOptions,
BaseProviderOptions,
StytchOtpAuthenticateOptions,
StytchToken,
} from '@lit-protocol/types';
import { BaseProvider } from './BaseProvider';
import { StytchOtpProviderOptions } from '@lit-protocol/types';

export class StytchOtpProvider extends BaseProvider {
private _params: StytchOtpProviderOptions;
private _provider: string = 'https://stytch.com/session';

constructor(params: BaseProviderOptions, config: StytchOtpProviderOptions) {
super(params);
this._params = config;
}

/**
* Validates claims within a stytch authenticated JSON Web Token
* @param options authentication option containing the authenticated token
* @returns {AuthMethod} Authentication Method for auth method type OTP
* */
override authenticate<T extends BaseAuthenticateOptions>(
options?: T | undefined
): Promise<AuthMethod> {
return new Promise<AuthMethod>((resolve, reject) => {
if (!options) {
reject(
new Error(
'No Authentication options provided, please supply an authenticated JWT'
)
);
}

const userId: string | undefined =
this._params.userId ??
(options as unknown as StytchOtpAuthenticateOptions).userId;

if (!userId) {
reject(new Error('User id must be provided'));
}
const accessToken: string | undefined = (
options as unknown as StytchOtpAuthenticateOptions
)?.accessToken;
if (!accessToken) {
reject(
new Error('No access token provided, please provide a stych auth jwt')
);
}

const parsedToken: StytchToken = this._parseJWT(accessToken);
console.log(`otpProvider: parsed token body`, parsedToken);
const audience = (parsedToken['aud'] as string[])[0];
if (audience != this._params.appId) {
reject(new Error('Parsed application id does not match parameters'));
}

if (!audience) {
reject(
new Error(
'could not find project id in token body, is this a stych token?'
)
);
}
const session = parsedToken[this._provider];
const authFactor = session['authentication_factors'][0];
if (!authFactor) {
reject(new Error('Could not find authentication info in session'));
}

if (userId != parsedToken['sub']) {
reject(
new Error(
'AppId does not match token contents. is this the right token for your application?'
)
);
}

resolve({
authMethodType: AuthMethodType.StytchOtp,
accessToken: accessToken,
});
});
}

/**
*
* @param jwt token to parse
* @returns {string}- userId contained within the token message
*/
private _parseJWT(jwt: string): StytchToken {
const parts = jwt.split('.');
if (parts.length !== 3) {
throw new Error('Invalid token length');
}
const body = Buffer.from(parts[1], 'base64');
const parsedBody: StytchToken = JSON.parse(body.toString('ascii'));
console.log('JWT body: ', parsedBody);
return parsedBody;
}
}
4 changes: 4 additions & 0 deletions packages/lit-auth-client/src/lib/relay.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,8 @@ export class LitRelay implements IRelay {
return '/auth/google/userinfo';
case AuthMethodType.OTP:
return `/auth/otp/userinfo`;
case AuthMethodType.StytchOtp:
return 'auth/stytch-otp/userinfo';
case AuthMethodType.WebAuthn:
return '/auth/webauthn/userinfo';
default:
Expand All @@ -233,6 +235,8 @@ export class LitRelay implements IRelay {
return '/auth/google';
case AuthMethodType.OTP:
return `/auth/otp`;
case AuthMethodType.StytchOtp:
return '/auth/stytch-otp';
case AuthMethodType.WebAuthn:
return '/auth/webauthn/verify-registration';
default:
Expand Down
51 changes: 47 additions & 4 deletions packages/types/src/lib/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,17 @@ export interface SignInWithOTPParams {
customName?: string;
}

export interface EthWalletProviderOptions {
/**
* The domain from which the signing request is made
*/
domain?: string;
/**
* The origin from which the signing request is made
*/
origin?: string;
}

export interface OtpProviderOptions {
baseUrl?: string;
port?: string;
Expand All @@ -1213,6 +1224,30 @@ export interface OtpEmailCustomizationOptions {
from?: string;
fromName: string;
}

export interface SignInWithStytchOTPParams {
// JWT from an authenticated session
// see stych docs for more info: https://stytch.com/docs/api/session-get
accessToken?: string;
// username or phone number where OTP was delivered
userId: string;
}

export interface StytchOtpProviderOptions {
/*
Stytch application identifier
*/
appId: string;
/*
Stytch user identifier for a project
*/
userId?: string;
}

export interface StytchToken {
[key: string]: any;
}

export interface BaseProviderSessionSigsParams {
/**
* Public key of PKP to auth with
Expand Down Expand Up @@ -1257,10 +1292,6 @@ export interface LoginUrlParams {

export interface BaseAuthenticateOptions {}

export interface OtpAuthenticateOptions {
code: string;
}

export interface EthWalletAuthenticateOptions extends BaseAuthenticateOptions {
/**
* Ethereum wallet address
Expand Down Expand Up @@ -1290,3 +1321,15 @@ export interface OtpAuthenticateOptions extends BaseAuthenticateOptions {
*/
code: string;
}

export interface StytchOtpAuthenticateOptions extends BaseAuthenticateOptions {
/*
* JWT from an authenticated session
* see stych docs for more info: https://stytch.com/docs/api/session-get
*/
accessToken: string;
/*
Stytch user identifier for a project
*/
userId?: string;
}