Skip to content

Commit

Permalink
Support creating dashboard wallet user in SDK (#7106)
Browse files Browse the repository at this point in the history
Co-authored-by: Nikki Kang <kangaroo233@gmail.com>
  • Loading branch information
nicoback2 and nicoback authored Jan 9, 2024
1 parent 98da601 commit 274bc9c
Show file tree
Hide file tree
Showing 10 changed files with 188 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,25 @@ export class DashboardWalletUsersApi extends GeneratedDashboardWalletUsersApi {
params: CreateDashboardWalletUserRequest,
advancedOptions?: AdvancedOptions
) {
const {
wallet,
userId,
walletSignature: { message, signature }
} = await parseParams(
'createDashboardWalletUser',
CreateDashboardWalletUser
)(params)
const { wallet, userId, walletSignature, userSignature } =
await parseParams(
'createDashboardWalletUser',
CreateDashboardWalletUser
)(params)

const signatureMetadata = walletSignature
? {
wallet_signature: {
message: walletSignature.message,
signature: walletSignature.signature
}
}
: {
user_signature: {
message: userSignature!.message,
signature: userSignature!.signature
}
}

const response = await this.entityManager.manageEntity({
userId,
Expand All @@ -49,10 +60,7 @@ export class DashboardWalletUsersApi extends GeneratedDashboardWalletUsersApi {
action: Action.CREATE,
metadata: JSON.stringify({
wallet,
wallet_signature: {
message,
signature
}
...signatureMetadata
}),
auth: this.auth,
...advancedOptions
Expand Down
34 changes: 24 additions & 10 deletions packages/libs/src/sdk/api/dashboard-wallet-users/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,31 @@ import { z } from 'zod'
import { HashId } from '../../types/HashId'
import { isEthAddressValid } from '../../utils/ethAddress'

export const CreateDashboardWalletUser = z.object({
wallet: z.custom<string>((data: unknown) => {
return isEthAddressValid(data as string)
}),
userId: HashId,
walletSignature: z.object({
/** Message should be of the form: "Connecting Audius user id a93jl at 39823489" */
message: z.string(),
signature: z.string()
export const CreateDashboardWalletUser = z
.object({
wallet: z.custom<string>((data: unknown) => {
return isEthAddressValid(data as string)
}),
userId: HashId,
walletSignature: z
.object({
/** Message should be of the form: "Connecting Audius user id a93jl at 39823489" */
message: z.string(),
signature: z.string()
})
.optional(),
userSignature: z
.object({
/** Message should be of the form: "Connecting Audius protocol dashboard wallet 0x6c9CA7D9580d4e8286B0628c0300A2A1235a8e2E at 39823489" */
message: z.string(),
signature: z.string()
})
.optional()
})
})
.refine(
(data) => !!data.userSignature || !!data.walletSignature,
"Either `userSignature` or `walletSignature` is required. Use `userSignature` if SDK is authenticated with the wallet's sign methods, and use `walletSignature` if SDK is authenticated with the user's sign methods."
)

export type CreateDashboardWalletUserRequest = z.input<
typeof CreateDashboardWalletUser
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ models/TrackResponse.ts
models/TrackSearch.ts
models/TracksResponse.ts
models/TrendingPlaylistsResponse.ts
models/TxSignature.ts
models/User.ts
models/UserAssociatedWalletResponse.ts
models/UserResponse.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ import {
ProfilePictureFromJSONTyped,
ProfilePictureToJSON,
} from './ProfilePicture';
import type { TxSignature } from './TxSignature';
import {
TxSignatureFromJSON,
TxSignatureFromJSONTyped,
TxSignatureToJSON,
} from './TxSignature';

/**
*
Expand Down Expand Up @@ -75,6 +81,12 @@ export interface DecodedUserToken {
* @memberof DecodedUserToken
*/
iat: string;
/**
*
* @type {TxSignature}
* @memberof DecodedUserToken
*/
txSignature?: TxSignature;
}

/**
Expand Down Expand Up @@ -111,6 +123,7 @@ export function DecodedUserTokenFromJSONTyped(json: any, ignoreDiscriminator: bo
'profilePicture': !exists(json, 'profilePicture') ? undefined : ProfilePictureFromJSON(json['profilePicture']),
'sub': json['sub'],
'iat': json['iat'],
'txSignature': !exists(json, 'txSignature') ? undefined : TxSignatureFromJSON(json['txSignature']),
};
}

Expand All @@ -131,6 +144,7 @@ export function DecodedUserTokenToJSON(value?: DecodedUserToken | null): any {
'profilePicture': ProfilePictureToJSON(value.profilePicture),
'sub': value.sub,
'iat': value.iat,
'txSignature': TxSignatureToJSON(value.txSignature),
};
}

76 changes: 76 additions & 0 deletions packages/libs/src/sdk/api/generated/default/models/TxSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
/**
* API
* Audius V1 API
*
* The version of the OpenAPI document: 1.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

import { exists, mapValues } from '../runtime';
/**
*
* @export
* @interface TxSignature
*/
export interface TxSignature {
/**
*
* @type {string}
* @memberof TxSignature
*/
message: string;
/**
*
* @type {string}
* @memberof TxSignature
*/
signature: string;
}

/**
* Check if a given object implements the TxSignature interface.
*/
export function instanceOfTxSignature(value: object): boolean {
let isInstance = true;
isInstance = isInstance && "message" in value;
isInstance = isInstance && "signature" in value;

return isInstance;
}

export function TxSignatureFromJSON(json: any): TxSignature {
return TxSignatureFromJSONTyped(json, false);
}

export function TxSignatureFromJSONTyped(json: any, ignoreDiscriminator: boolean): TxSignature {
if ((json === undefined) || (json === null)) {
return json;
}
return {

'message': json['message'],
'signature': json['signature'],
};
}

export function TxSignatureToJSON(value?: TxSignature | null): any {
if (value === undefined) {
return undefined;
}
if (value === null) {
return null;
}
return {

'message': value.message,
'signature': value.signature,
};
}

Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ export * from './TrackResponse';
export * from './TrackSearch';
export * from './TracksResponse';
export * from './TrendingPlaylistsResponse';
export * from './TxSignature';
export * from './User';
export * from './UserAssociatedWalletResponse';
export * from './UserResponse';
Expand Down
8 changes: 8 additions & 0 deletions packages/libs/src/sdk/api/generated/full/models/TrackFull.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,12 @@ export interface TrackFull {
* @memberof TrackFull
*/
hasCurrentUserReposted: boolean;
/**
*
* @type {boolean}
* @memberof TrackFull
*/
isScheduledRelease?: boolean;
/**
*
* @type {boolean}
Expand Down Expand Up @@ -449,6 +455,7 @@ export function TrackFullFromJSONTyped(json: any, ignoreDiscriminator: boolean):
'fieldVisibility': !exists(json, 'field_visibility') ? undefined : FieldVisibilityFromJSON(json['field_visibility']),
'followeeReposts': ((json['followee_reposts'] as Array<any>).map(RepostFromJSON)),
'hasCurrentUserReposted': json['has_current_user_reposted'],
'isScheduledRelease': !exists(json, 'is_scheduled_release') ? undefined : json['is_scheduled_release'],
'isUnlisted': json['is_unlisted'],
'hasCurrentUserSaved': json['has_current_user_saved'],
'followeeFavorites': ((json['followee_favorites'] as Array<any>).map(FavoriteFromJSON)),
Expand Down Expand Up @@ -510,6 +517,7 @@ export function TrackFullToJSON(value?: TrackFull | null): any {
'field_visibility': FieldVisibilityToJSON(value.fieldVisibility),
'followee_reposts': ((value.followeeReposts as Array<any>).map(RepostToJSON)),
'has_current_user_reposted': value.hasCurrentUserReposted,
'is_scheduled_release': value.isScheduledRelease,
'is_unlisted': value.isUnlisted,
'has_current_user_saved': value.hasCurrentUserSaved,
'followee_favorites': ((value.followeeFavorites as Array<any>).map(FavoriteToJSON)),
Expand Down
36 changes: 30 additions & 6 deletions packages/libs/src/sdk/oauth/OAuth.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import type { DecodedUserToken, UsersApi } from '../api/generated/default'
import type { LoggerService } from '../services/Logger'
import { isOAuthScopeValid } from '../utils/oauthScope'
import { isOAuthScopeValid, isWriteOnceParams } from '../utils/oauthScope'
import { parseParams } from '../utils/parseParams'

import {
OAuthScope,
IsWriteAccessGrantedSchema,
IsWriteAccessGrantedRequest
IsWriteAccessGrantedRequest,
WriteOnceParams
} from './types'

export type LoginSuccessCallback = (profile: DecodedUserToken) => void
Expand Down Expand Up @@ -188,13 +189,19 @@ export class OAuth {
return foundIndex !== undefined && foundIndex > -1
}

login({ scope = 'read' }: { scope?: OAuthScope }) {
login({
scope = 'read',
params
}: {
scope?: OAuthScope
params?: WriteOnceParams
}) {
const scopeFormatted = typeof scope === 'string' ? [scope] : scope
if (!this.config.appName && !this.apiKey) {
this._surfaceError('App name not set (set with `init` method).')
return
}
if (scope.includes('write') && !this.apiKey) {
if (scopeFormatted.includes('write') && !this.apiKey) {
this._surfaceError(
"The 'write' scope requires Audius SDK to be initialized with an API key"
)
Expand All @@ -210,6 +217,16 @@ export class OAuth {
return
}

const effectiveScope = scopeFormatted.includes('write')
? 'write'
: scopeFormatted.includes('write_once')
? 'write_once'
: 'read'
if (effectiveScope === 'write_once' && !isWriteOnceParams(params)) {
this._surfaceError('Missing correct params for `oauth.login`.')
return
}

const csrfToken = generateId()
window.localStorage.setItem(CSRF_TOKEN_KEY, csrfToken)
const windowOptions =
Expand All @@ -218,13 +235,20 @@ export class OAuth {
const appIdURISafe = encodeURIComponent(
(this.apiKey || this.config.appName)!
)

const writeOnceParams =
effectiveScope !== 'write_once'
? ''
: `&tx=${encodeURIComponent(params!.tx)}&wallet=${encodeURIComponent(
params!.wallet
)}`
const appIdURIParam = `${
this.apiKey ? 'api_key' : 'app_name'
}=${appIdURISafe}`
const scopeUriParam = scope.includes('write') ? 'write' : 'read'

const fullOauthUrl = `${
OAUTH_URL[this.env]
}?scope=${scopeUriParam}&state=${csrfToken}&redirect_uri=postMessage&origin=${originURISafe}&${appIdURIParam}`
}?scope=${effectiveScope}&state=${csrfToken}&redirect_uri=postMessage&origin=${originURISafe}&${appIdURIParam}${writeOnceParams}`
this.activePopupWindow = window.open(fullOauthUrl, '', windowOptions)
this._clearPopupCheckInterval()
this.popupCheckInterval = setInterval(() => {
Expand Down
6 changes: 5 additions & 1 deletion packages/libs/src/sdk/oauth/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ export type IsWriteAccessGrantedRequest = z.input<
typeof IsWriteAccessGrantedSchema
>

export const OAUTH_SCOPE_OPTIONS = ['read', 'write'] as const
export const OAUTH_SCOPE_OPTIONS = ['read', 'write', 'write_once'] as const
type OAuthScopesTuple = typeof OAUTH_SCOPE_OPTIONS
export type OAuthScopeOption = OAuthScopesTuple[number]
export type OAuthScope = OAuthScopeOption | OAuthScopeOption[]
export type WriteOnceParams = {
tx: 'connect_dashboard_wallet'
wallet: string
} // | ...
10 changes: 9 additions & 1 deletion packages/libs/src/sdk/utils/oauthScope.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { OAUTH_SCOPE_OPTIONS } from '../oauth'
import { OAUTH_SCOPE_OPTIONS, WriteOnceParams } from '../oauth'

export const isOAuthScopeValid = (scope: string[]) => {
const validScopes = new Set(OAUTH_SCOPE_OPTIONS)
return scope.findIndex((s) => !validScopes.has(s as any)) === -1
}

export const isWriteOnceParams = (object: any): object is WriteOnceParams => {
return (
'tx' in object &&
object.tx === 'connect_dashboard_wallet' &&
'wallet' in object
)
}

0 comments on commit 274bc9c

Please sign in to comment.