All files / src/connect authentication.ts

29.72% Statements 11/37
0% Branches 0/23
0% Functions 0/5
29.72% Lines 11/37

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 872x 2x 2x 2x   2x 2x           2x                                                 2x                                       2x         2x                 2x                              
import { bytesToHex, getGlobalObject } from 'micro-stacks/common';
import { getPublicKey, getRandomBytes, TokenSigner } from 'micro-stacks/crypto';
import { decodeAuthResponse } from './auth/decode-auth-response';
import { getStacksProvider } from './common/get-stacks-provider';
 
import { PersistedDataKeys } from './common/constants';
import { defaultStorageAdapter } from './common/utils';
 
import type { StacksProvider } from './common/provider';
import type { Json } from 'micro-stacks/crypto';
import type { AuthRequestPayload, AuthOptions, AuthScope } from './auth/types';
 
export async function authenticate(
  authOptions: AuthOptions,
  storageAdapter = defaultStorageAdapter,
  serialize = JSON.stringify
) {
  Iif (!authOptions.appDetails) {
    throw Error(
      '[micro-stacks] authenticate error: `authOptions.appDetails` are required for authentication'
    );
  }
  try {
    const transitPrivateKey = bytesToHex(getRandomBytes());
    const authResponseToken = await handleAuthResponse(authOptions, transitPrivateKey);
    const sessionState = await decodeAuthResponse(authResponseToken, transitPrivateKey);
 
    authOptions?.onFinish?.(sessionState);
    storageAdapter.setItem(PersistedDataKeys.SessionStorageKey, serialize(sessionState));
 
    return sessionState;
  } catch (e) {
    // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
    authOptions?.onCancel?.((e as any).message);
  }
}
 
export function generateAuthRequestPayload(authOptions: AuthOptions, transitPublicKey: string) {
  Iif (!authOptions.appDetails) {
    throw Error(
      '[micro-stacks] authenticate error: `authOptions.appDetails` are required for authentication'
    );
  }
  const _scopes = authOptions.scopes || [];
 
  const origin = getGlobalObject('location', { returnEmptyObject: true })!.origin;
  const payload: AuthRequestPayload = {
    scopes: [...new Set(['store_write', ..._scopes])] as AuthScope[],
    redirect_uri: origin,
    public_keys: [transitPublicKey],
    domain_name: origin,
    appDetails: authOptions.appDetails,
  };
 
  return payload;
}
 
export async function signAuthRequest(payload: unknown, transitPrivateKey: string) {
  const signer = new TokenSigner('ES256k', transitPrivateKey);
  return signer.sign(payload as unknown as Json);
}
 
export async function generateSignedAuthRequest(
  authOptions: AuthOptions,
  transitPrivateKey: string
) {
  const transitPublicKey = bytesToHex(getPublicKey(transitPrivateKey));
  const payload = generateAuthRequestPayload(authOptions, transitPublicKey);
  return signAuthRequest(payload, transitPrivateKey);
}
 
export async function handleAuthResponse(authOptions: AuthOptions, transitPrivateKey: string) {
  const Provider: StacksProvider | undefined = getStacksProvider();
  Iif (!Provider)
    throw Error(
      'This function can only be called on the client, and with the presence of StacksProvider'
    );
  const authRequest = await generateSignedAuthRequest(authOptions, transitPrivateKey);
  return Provider!.authenticationRequest(authRequest);
}
 
declare global {
  interface Window {
    StacksProvider?: StacksProvider;
  }
}