Skip to content

Commit

Permalink
feat(wallet/backend): add endpoints for view/change pin (#1659)
Browse files Browse the repository at this point in the history
* Add getPin endpoint

* Add changePin endpoint, tests

* Fix tests

* Small fixes

* Update status code for change pin
  • Loading branch information
sanducb authored Oct 1, 2024
1 parent d3e3236 commit 237b91a
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 16 deletions.
2 changes: 2 additions & 0 deletions packages/wallet/backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ export class App {
router.get('/cards/:cardId/details', isAuth, cardController.getCardDetails)
router.put('/cards/:cardId/lock', isAuth, cardController.lock)
router.put('/cards/:cardId/unlock', isAuth, cardController.unlock)
router.get('/cards/:cardId/pin', isAuth, cardController.getPin)
router.post('/cards/:cardId/change-pin', isAuth, cardController.changePin)

// Return an error for invalid routes
router.use('*', (req: Request, res: CustomResponse) => {
Expand Down
42 changes: 39 additions & 3 deletions packages/wallet/backend/src/card/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ import {
getCardsByCustomerSchema,
getCardDetailsSchema,
lockCardSchema,
unlockCardSchema
unlockCardSchema,
changePinSchema
} from './validation'

export interface ICardController {
getCardsByCustomer: Controller<ICardDetailsResponse[]>
getCardDetails: Controller<ICardResponse>
lock: Controller<ICardResponse>
unlock: Controller<ICardResponse>
getPin: Controller<ICardResponse>
changePin: Controller<void>
}

export class CardController implements ICardController {
Expand Down Expand Up @@ -50,9 +53,9 @@ export class CardController implements ICardController {
) => {
try {
const userId = req.session.user.id
const { params, body } = await validate(getCardDetailsSchema, req)
const { params, query } = await validate(getCardDetailsSchema, req)
const { cardId } = params
const { publicKeyBase64 } = body
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const cardDetails = await this.cardService.getCardDetails(
Expand Down Expand Up @@ -97,4 +100,37 @@ export class CardController implements ICardController {
next(error)
}
}

public getPin = async (req: Request, res: Response, next: NextFunction) => {
try {
const userId = req.session.user.id
const { params, query } = await validate(getCardDetailsSchema, req)
const { cardId } = params
const { publicKeyBase64 } = query

const requestBody: ICardDetailsRequest = { cardId, publicKeyBase64 }
const cardPin = await this.cardService.getPin(userId, requestBody)
res.status(200).json(toSuccessResponse(cardPin))
} catch (error) {
next(error)
}
}

public changePin = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
const userId = req.session.user.id
const { params, body } = await validate(changePinSchema, req)
const { cardId } = params
const { cypher } = body

const result = await this.cardService.changePin(userId, cardId, cypher)
res.status(201).json(toSuccessResponse(result))
} catch (error) {
next(error)
}
}
}
42 changes: 34 additions & 8 deletions packages/wallet/backend/src/card/service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,18 +25,31 @@ export class CardService {
requestBody: ICardDetailsRequest
): Promise<ICardDetailsResponse> {
const { cardId } = requestBody

const walletAddress = await this.walletAddressService.getByCardId(
userId,
cardId
)
if (!walletAddress) {
throw new NotFound('Card not found or not associated with the user.')
}
await this.ensureWalletAddressExists(userId, cardId)

return this.gateHubClient.getCardDetails(requestBody)
}

async getPin(
userId: string,
requestBody: ICardDetailsRequest
): Promise<ICardDetailsResponse> {
const { cardId } = requestBody
await this.ensureWalletAddressExists(userId, cardId)

return this.gateHubClient.getPin(requestBody)
}

async changePin(
userId: string,
cardId: string,
cypher: string
): Promise<void> {
await this.ensureWalletAddressExists(userId, cardId)

await this.gateHubClient.changePin(cardId, cypher)
}

async lock(
cardId: string,
reasonCode: LockReasonCode,
Expand All @@ -51,4 +64,17 @@ export class CardService {
): Promise<ICardResponse> {
return this.gateHubClient.unlockCard(cardId, requestBody)
}

private async ensureWalletAddressExists(
userId: string,
cardId: string
): Promise<void> {
const walletAddress = await this.walletAddressService.getByCardId(
userId,
cardId
)
if (!walletAddress) {
throw new NotFound('Card not found or not associated with the user.')
}
}
}
11 changes: 10 additions & 1 deletion packages/wallet/backend/src/card/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export const getCardDetailsSchema = z.object({
params: z.object({
cardId: z.string()
}),
body: z.object({
query: z.object({
publicKeyBase64: z.string()
})
})
Expand Down Expand Up @@ -42,3 +42,12 @@ export const unlockCardSchema = z.object({
note: z.string()
})
})

export const changePinSchema = z.object({
params: z.object({
cardId: z.string()
}),
body: z.object({
cypher: z.string()
})
})
64 changes: 64 additions & 0 deletions packages/wallet/backend/src/gatehub/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,70 @@ export class GateHubClient {
)
}

async getPin(
requestBody: ICardDetailsRequest
): Promise<ICardDetailsResponse> {
const url = `${this.apiUrl}/token/pin`

const response = await this.request<ILinksResponse>(
'POST',
url,
JSON.stringify(requestBody),
{
cardAppId: this.env.GATEHUB_CARD_APP_ID
}
)

const token = response.token
if (!token) {
throw new Error('Failed to obtain token for card pin retrieval')
}

// TODO change this to direct call to card managing entity
// Will get this from the GateHub proxy for now
const cardPinUrl = `${this.apiUrl}/v1/proxy/client-device/pin`
const cardPinResponse = await this.request<ICardDetailsResponse>(
'GET',
cardPinUrl,
undefined,
{
token
}
)

return cardPinResponse
}

async changePin(cardId: string, cypher: string): Promise<void> {
const url = `${this.apiUrl}/token/pin-change`

const response = await this.request<ILinksResponse>(
'POST',
url,
JSON.stringify({ cardId: cardId }),
{
cardAppId: this.env.GATEHUB_CARD_APP_ID
}
)

const token = response.token
if (!token) {
throw new Error('Failed to obtain token for card pin retrieval')
}

// TODO change this to direct call to card managing entity
// Will get this from the GateHub proxy for now
const cardPinUrl = `${this.apiUrl}/v1/proxy/client-device/pin`
await this.request<void>(
'POST',
cardPinUrl,
JSON.stringify({ cypher: cypher }),
{
token
}
)
}

private async request<T>(
method: HTTP_METHODS,
url: string,
Expand Down
Loading

0 comments on commit 237b91a

Please sign in to comment.