From b7c50aa33176aecd4317a5ad72c9d8c2b986b8db Mon Sep 17 00:00:00 2001 From: Nicolas Burtey Date: Tue, 30 Apr 2024 16:46:35 -0600 Subject: [PATCH] feat: exposing delegation --- apps/consent/app/graphql/generated.ts | 10 +++++++++ apps/dashboard/services/graphql/generated.ts | 22 +++++++++++++++++++ apps/map/services/galoy/graphql/generated.ts | 10 +++++++++ apps/pay/lib/graphql/generated.ts | 10 +++++++++ bats/core/api/user.bats | 7 ++++++ bats/gql/list-sessions.gql | 6 +++++ core/api/src/app/users/list-sessions.ts | 9 ++++---- .../domain/authentication/index.types.d.ts | 2 +- core/api/src/graphql/public/schema.graphql | 10 +++++++++ .../public/types/object/deleguation.ts | 15 +++++++++++++ .../src/graphql/public/types/object/user.ts | 11 +++++++++- core/api/src/services/hydra/index.ts | 6 +++-- .../integration/app/user/consent-list.spec.ts | 5 +++-- .../apollo-federation/supergraph.graphql | 12 ++++++++++ 14 files changed, 124 insertions(+), 11 deletions(-) create mode 100644 core/api/src/graphql/public/types/object/deleguation.ts diff --git a/apps/consent/app/graphql/generated.ts b/apps/consent/app/graphql/generated.ts index e689c2696d..3958a65f86 100644 --- a/apps/consent/app/graphql/generated.ts +++ b/apps/consent/app/graphql/generated.ts @@ -449,6 +449,14 @@ export type CurrencyConversionEstimation = { readonly usdCentAmount: Scalars['CentAmount']['output']; }; +export type Delegation = { + readonly __typename: 'Delegation'; + readonly app: Scalars['String']['output']; + readonly handledAt: Scalars['Timestamp']['output']; + readonly remember: Scalars['Boolean']['output']; + readonly scope: ReadonlyArray; +}; + export type DepositFeesInformation = { readonly __typename: 'DepositFeesInformation'; readonly minBankFee: Scalars['String']['output']; @@ -1921,6 +1929,8 @@ export type User = { readonly contacts: ReadonlyArray; readonly createdAt: Scalars['Timestamp']['output']; readonly defaultAccount: Account; + /** List of Oauth2 delegations */ + readonly delegations: ReadonlyArray; /** Email address */ readonly email?: Maybe; readonly id: Scalars['ID']['output']; diff --git a/apps/dashboard/services/graphql/generated.ts b/apps/dashboard/services/graphql/generated.ts index 82fe5a8b81..97685a0279 100644 --- a/apps/dashboard/services/graphql/generated.ts +++ b/apps/dashboard/services/graphql/generated.ts @@ -489,6 +489,14 @@ export type CurrencyConversionEstimation = { readonly usdCentAmount: Scalars['CentAmount']['output']; }; +export type Delegation = { + readonly __typename: 'Delegation'; + readonly app: Scalars['String']['output']; + readonly handledAt: Scalars['Timestamp']['output']; + readonly remember: Scalars['Boolean']['output']; + readonly scope: ReadonlyArray; +}; + export type DepositFeesInformation = { readonly __typename: 'DepositFeesInformation'; readonly minBankFee: Scalars['String']['output']; @@ -2018,6 +2026,8 @@ export type User = { readonly contacts: ReadonlyArray; readonly createdAt: Scalars['Timestamp']['output']; readonly defaultAccount: Account; + /** List of Oauth2 delegations */ + readonly delegations: ReadonlyArray; /** Email address */ readonly email?: Maybe; readonly id: Scalars['ID']['output']; @@ -3471,6 +3481,7 @@ export type ResolversTypes = { CountryCode: ResolverTypeWrapper; Currency: ResolverTypeWrapper; CurrencyConversionEstimation: ResolverTypeWrapper; + Delegation: ResolverTypeWrapper; DepositFeesInformation: ResolverTypeWrapper; DeviceNotificationTokenCreateInput: DeviceNotificationTokenCreateInput; DisplayCurrency: ResolverTypeWrapper; @@ -3699,6 +3710,7 @@ export type ResolversParentTypes = { CountryCode: Scalars['CountryCode']['output']; Currency: Currency; CurrencyConversionEstimation: CurrencyConversionEstimation; + Delegation: Delegation; DepositFeesInformation: DepositFeesInformation; DeviceNotificationTokenCreateInput: DeviceNotificationTokenCreateInput; DisplayCurrency: Scalars['DisplayCurrency']['output']; @@ -4139,6 +4151,14 @@ export type CurrencyConversionEstimationResolvers; }; +export type DelegationResolvers = { + app?: Resolver; + handledAt?: Resolver; + remember?: Resolver; + scope?: Resolver, ParentType, ContextType>; + __isTypeOf?: IsTypeOfResolverFn; +}; + export type DepositFeesInformationResolvers = { minBankFee?: Resolver; minBankFeeThreshold?: Resolver; @@ -4835,6 +4855,7 @@ export type UserResolvers, ParentType, ContextType>; createdAt?: Resolver; defaultAccount?: Resolver; + delegations?: Resolver, ParentType, ContextType>; email?: Resolver, ParentType, ContextType>; id?: Resolver; language?: Resolver; @@ -4992,6 +5013,7 @@ export type Resolvers = { CountryCode?: GraphQLScalarType; Currency?: CurrencyResolvers; CurrencyConversionEstimation?: CurrencyConversionEstimationResolvers; + Delegation?: DelegationResolvers; DepositFeesInformation?: DepositFeesInformationResolvers; DisplayCurrency?: GraphQLScalarType; Email?: EmailResolvers; diff --git a/apps/map/services/galoy/graphql/generated.ts b/apps/map/services/galoy/graphql/generated.ts index 279cf2cb2a..0dce789e6d 100644 --- a/apps/map/services/galoy/graphql/generated.ts +++ b/apps/map/services/galoy/graphql/generated.ts @@ -449,6 +449,14 @@ export type CurrencyConversionEstimation = { readonly usdCentAmount: Scalars['CentAmount']['output']; }; +export type Delegation = { + readonly __typename: 'Delegation'; + readonly app: Scalars['String']['output']; + readonly handledAt: Scalars['Timestamp']['output']; + readonly remember: Scalars['Boolean']['output']; + readonly scope: ReadonlyArray; +}; + export type DepositFeesInformation = { readonly __typename: 'DepositFeesInformation'; readonly minBankFee: Scalars['String']['output']; @@ -1921,6 +1929,8 @@ export type User = { readonly contacts: ReadonlyArray; readonly createdAt: Scalars['Timestamp']['output']; readonly defaultAccount: Account; + /** List of Oauth2 delegations */ + readonly delegations: ReadonlyArray; /** Email address */ readonly email?: Maybe; readonly id: Scalars['ID']['output']; diff --git a/apps/pay/lib/graphql/generated.ts b/apps/pay/lib/graphql/generated.ts index ccd77bf613..a114aef2d8 100644 --- a/apps/pay/lib/graphql/generated.ts +++ b/apps/pay/lib/graphql/generated.ts @@ -448,6 +448,14 @@ export type CurrencyConversionEstimation = { readonly usdCentAmount: Scalars['CentAmount']; }; +export type Delegation = { + readonly __typename: 'Delegation'; + readonly app: Scalars['String']; + readonly handledAt: Scalars['Timestamp']; + readonly remember: Scalars['Boolean']; + readonly scope: ReadonlyArray; +}; + export type DepositFeesInformation = { readonly __typename: 'DepositFeesInformation'; readonly minBankFee: Scalars['String']; @@ -1920,6 +1928,8 @@ export type User = { readonly contacts: ReadonlyArray; readonly createdAt: Scalars['Timestamp']; readonly defaultAccount: Account; + /** List of Oauth2 delegations */ + readonly delegations: ReadonlyArray; /** Email address */ readonly email?: Maybe; readonly id: Scalars['ID']; diff --git a/bats/core/api/user.bats b/bats/core/api/user.bats index e8fe4ce578..6e5579e4de 100644 --- a/bats/core/api/user.bats +++ b/bats/core/api/user.bats @@ -26,3 +26,10 @@ setup_file() { language="$(graphql_output '.data.me.language')" [[ "$language" == "$new_language" ]] || exit 1 } + +@test "user: list sessions" { + exec_graphql 'alice' 'list-sessions' + sessions="$(graphql_output '.data.me.mobileSessions')" + id="$(echo "$sessions" | jq -r '.[0].id')" # Extracts the ID of the first element + [[ "$sessions" != "[]" ]] && [[ -n "$id" ]] || exit 1 +} diff --git a/bats/gql/list-sessions.gql b/bats/gql/list-sessions.gql index fa43e65b7e..5a5ee558a6 100644 --- a/bats/gql/list-sessions.gql +++ b/bats/gql/list-sessions.gql @@ -7,5 +7,11 @@ query userDetails { expiresAt issuedAt } + delegations { + app + handledAt + remember + scope + } } } diff --git a/core/api/src/app/users/list-sessions.ts b/core/api/src/app/users/list-sessions.ts index a08885cf38..a122d83006 100644 --- a/core/api/src/app/users/list-sessions.ts +++ b/core/api/src/app/users/list-sessions.ts @@ -1,11 +1,10 @@ +import { consentList } from "@/services/hydra" import { listSessions as listSessionsService } from "@/services/kratos" export const listMobileSessions = async (userId: UserId) => { - const list = await listSessionsService(userId) - console.dir(list) - return list + return listSessionsService(userId) } -export const listDeleguateSessions = async (userId: UserId) => { - +export const listDeleguations = async (userId: UserId) => { + return consentList(userId) } diff --git a/core/api/src/domain/authentication/index.types.d.ts b/core/api/src/domain/authentication/index.types.d.ts index 8863f43b20..d8a9840931 100644 --- a/core/api/src/domain/authentication/index.types.d.ts +++ b/core/api/src/domain/authentication/index.types.d.ts @@ -68,7 +68,7 @@ type MobileSession = { issuedAt: Date } -type ConsentSession = { +type Delegation = { scope: string[] handledAt: Date remember: boolean diff --git a/core/api/src/graphql/public/schema.graphql b/core/api/src/graphql/public/schema.graphql index 0e693edd93..c8b6dd7e01 100644 --- a/core/api/src/graphql/public/schema.graphql +++ b/core/api/src/graphql/public/schema.graphql @@ -354,6 +354,13 @@ type CurrencyConversionEstimation { usdCentAmount: CentAmount! } +type Delegation { + app: String! + handledAt: Timestamp! + remember: Boolean! + scope: [String!]! +} + type DepositFeesInformation { minBankFee: String! @@ -1595,6 +1602,9 @@ type User { createdAt: Timestamp! defaultAccount: Account! + """List of Oauth2 delegations""" + delegations: [Delegation!]! + """Email address""" email: Email id: ID! diff --git a/core/api/src/graphql/public/types/object/deleguation.ts b/core/api/src/graphql/public/types/object/deleguation.ts new file mode 100644 index 0000000000..2e714efe63 --- /dev/null +++ b/core/api/src/graphql/public/types/object/deleguation.ts @@ -0,0 +1,15 @@ +import Timestamp from "@/graphql/shared/types/scalar/timestamp" + +import { GT } from "@/graphql/index" + +const Delegation = GT.Object({ + name: "Delegation", + fields: () => ({ + app: { type: GT.NonNull(GT.String) }, + handledAt: { type: GT.NonNull(Timestamp) }, + remember: { type: GT.NonNull(GT.Boolean) }, + scope: { type: GT.NonNullList(GT.String) }, + }), +}) + +export default Delegation diff --git a/core/api/src/graphql/public/types/object/user.ts b/core/api/src/graphql/public/types/object/user.ts index 51b7f6772b..e75af282f9 100644 --- a/core/api/src/graphql/public/types/object/user.ts +++ b/core/api/src/graphql/public/types/object/user.ts @@ -4,6 +4,7 @@ import Account from "../abstract/account" import SupportMessage from "./support-message" import AccountContact from "./account-contact" +import Delegation from "./deleguation" import { Accounts, Users, SupportChat } from "@/app" @@ -19,7 +20,7 @@ import Language from "@/graphql/shared/types/scalar/language" import Username from "@/graphql/shared/types/scalar/username" import Timestamp from "@/graphql/shared/types/scalar/timestamp" import GraphQLEmail from "@/graphql/shared/types/object/email" -import MobileSession from "./mobile-session" +import MobileSession from "@/graphql/public/types/object/mobile-session" const GraphQLUser = GT.Object({ name: "User", @@ -84,6 +85,14 @@ const GraphQLUser = GT.Object({ }, }, + delegations: { + type: GT.NonNullList(Delegation), + description: "List of Oauth2 delegations", + resolve: async (source, args, { user }) => { + return Users.listDeleguations(user.id) + }, + }, + contacts: { deprecationReason: "will be moved to account", type: GT.NonNullList(AccountContact), // TODO: Make it a Connection Interface diff --git a/core/api/src/services/hydra/index.ts b/core/api/src/services/hydra/index.ts index cef1a89daf..3e08646bf7 100644 --- a/core/api/src/services/hydra/index.ts +++ b/core/api/src/services/hydra/index.ts @@ -2,7 +2,9 @@ import axios, { AxiosResponse } from "axios" const hydraUrl = process.env.HYDRA_ADMIN_URL || "http://localhost:4445" -export const consentList = async (userId: UserId): Promise => { +export const consentList = async (userId: UserId): Promise => { + /* eslint @typescript-eslint/ban-ts-comment: "off" */ + // @ts-ignore-next-line no-implicit-any error let res: AxiosResponse try { @@ -15,7 +17,7 @@ export const consentList = async (userId: UserId): Promise => return [] } - let sessions: ConsentSession[] + let sessions: Delegation[] try { /* eslint @typescript-eslint/ban-ts-comment: "off" */ diff --git a/core/api/test/integration/app/user/consent-list.spec.ts b/core/api/test/integration/app/user/consent-list.spec.ts index a6bbe5b83c..a88f00e15c 100644 --- a/core/api/test/integration/app/user/consent-list.spec.ts +++ b/core/api/test/integration/app/user/consent-list.spec.ts @@ -1,8 +1,9 @@ +import { exec } from "child_process" +import puppeteer from "puppeteer" + import { Admin } from "@/app" import { consentList } from "@/services/hydra" import { sleep } from "@/utils" -import { exec } from "child_process" -import puppeteer from "puppeteer" let userId: UserId const email = "test@galoy.io" as EmailAddress diff --git a/dev/config/apollo-federation/supergraph.graphql b/dev/config/apollo-federation/supergraph.graphql index 35daf5baeb..e9db449eeb 100644 --- a/dev/config/apollo-federation/supergraph.graphql +++ b/dev/config/apollo-federation/supergraph.graphql @@ -486,6 +486,15 @@ type CurrencyConversionEstimation usdCentAmount: CentAmount! } +type Delegation + @join__type(graph: PUBLIC) +{ + app: String! + handledAt: Timestamp! + remember: Boolean! + scope: [String!]! +} + type DepositFeesInformation @join__type(graph: PUBLIC) { @@ -2085,6 +2094,9 @@ type User createdAt: Timestamp! @join__field(graph: PUBLIC) defaultAccount: Account! @join__field(graph: PUBLIC) + """List of Oauth2 delegations""" + delegations: [Delegation!]! @join__field(graph: PUBLIC) + """Email address""" email: Email @join__field(graph: PUBLIC)