Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .github/CODEOWNERS

This file was deleted.

24 changes: 24 additions & 0 deletions .github/workflows/pr-build-auth-relay.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: PR - Build & test Auth Relay Service

on:
pull_request:
paths:
- "Project/**"
- "UnitTests/**"
- ".github/workflows/pr-build-auth-relay.yml"

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Restore dependencies
run: dotnet restore
- name: Build
run: dotnet build
- name: Test
run: dotnet test
22 changes: 0 additions & 22 deletions .github/workflows/validate_catalog.yaml

This file was deleted.

9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
**/bin/
**/obj/

.idea/
.vscode/

*.DotSettings.user

!Project/Properties/**
25 changes: 25 additions & 0 deletions ClientSecretDecryption/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Client Secrets

A Client Id/Secret pair is used by the Auth Relay Server to request a bearer token used to access Walmart account linking and checkout APIs. For security purposes the Client Secret is returned to developers via the Unity Dashboard portal encrypted using the public key previously uploaded.

## Client Secret Decryption using openssl

Using command line tools available on most linux systems the public key can be easily decrypted.

```bash
# Decrypt the client secret using the private key
base64 -d -i /path/to/credentials_encrypted.key | openssl pkeyutl -decrypt -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256 -inkey /path/to/private_key.pem
```

## Client Secret Decryption using Python

A sample script has been provided, [decrypt_client_secret.py](decrypt_client_secret.py), to decrypt the client secret using the private key. The encrypted secret can either be provided as a filename on the command line or piped to the script.

### Examples

```bash
# Decrypt the client secret using the private key
python3 decrypt_client_secret.py /path/to/private_key.pem /path/to/encrypted_client_secret.txt

cat /path/to/encrypted_client_secret.txt | python3 decrypt_client_secret.py /path/to/private_key.pem
```
78 changes: 78 additions & 0 deletions ClientSecretDecryption/decrypt_client_secret.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import sys
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.hazmat.backends import default_backend
import base64

def read_secret(secret_file=None):
"""
Read a secret either from stdin or from a file

:param secret_file: Path to the file containing the secret, or None to read from stdin
:return: The secret as a string
"""

if secret_file is None:
return sys.stdin.read()
else:
with open(secret_file, "r") as secret_input:
return secret_input.read()


def decrypt_with_rsa_private_key(encrypted_data, private_key_pem_file, password=None):
"""
Decrypt data using an RSA private key

:param encrypted_data: Base64 decoded client secret from Walmart
:param private_key_pem_file: Your private key file
:param password: Optional password for the private key
:return: The decrypted client secret
"""

with open(private_key_pem_file, "rb") as private_key_pem:
# Load the private key
private_key = load_pem_private_key(private_key_pem.read(), password=password, backend=default_backend())

# Decrypt the data
decrypted_data = private_key.decrypt(
encrypted_data,
padding.OAEP(
mgf=padding.MGF1(algorithm=hashes.SHA256()),
algorithm=hashes.SHA256(),
label=None
)
)
return decrypted_data


if __name__ == "__main__":
"""
Usage: python decrypt_client_secret.py <private_key_file> [<encrypted_client_secret_file>]

Decrypts the encrypted client secret using the provided private key. If the client secret file is not provided, it reads from stdin.

Example:
python decrypt_client_secret.py /path/to/private_key.pem /path/to/encrypted_client_secret.txt
or
cat /path/to/encrypted_client_secret.txt | python decrypt_client_secret.py /path/to/private_key.pem
"""

if len(sys.argv) < 3:
client_secret = read_secret()
else:
client_secret = read_secret(sys.argv[2])

try:
decoded_client_secret = base64.b64decode(client_secret)
except (binascii.Error, ValueError) as e:
print(f"Error: Failed to decode base64 client secret. {e}", file=sys.stderr)
sys.exit(1)

try:
results = decrypt_with_rsa_private_key(decoded_client_secret, sys.argv[1])
except Exception as e:
print(f"Error: Failed to decrypt client secret. {e}", file=sys.stderr)
sys.exit(1)

print(results.decode('utf-8'))
64 changes: 64 additions & 0 deletions Project/AuthRelayModule.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.WalmartAuthRelay.Contracts;
using Unity.WalmartAuthRelay.Interfaces;
using Unity.Services.CloudCode.Apis;
using Unity.Services.CloudCode.Core;

namespace Unity.WalmartAuthRelay;

public class AuthRelayModule
{
private readonly IWalmartCommerceService _commerceService;

public AuthRelayModule(IWalmartCommerceService commerceService)
{
_commerceService = commerceService;
}

[CloudCodeFunction("GetLoginUrl")]
public async Task<LoginUrlResponse> GetLoginUrlAsync(IExecutionContext ctx, IGameApiClient client, string? platform = null)
{
return await _commerceService.GetLoginUrlAsync(ctx, client, platform);
}

[CloudCodeFunction("LinkAccount")]
public async Task<LinkAccountResponse> LinkAccountAsync(IExecutionContext ctx, IGameApiClient client, string authorizationCode)
{
return await _commerceService.LinkAccountAsync(ctx, client, authorizationCode);
}

[CloudCodeFunction("UnlinkAccount")]
public async Task<UnlinkAccountResponse> UnlinkAccountAsync(IExecutionContext ctx, IGameApiClient client)
{
return await _commerceService.UnlinkAccountAsync(ctx, client);
}

[CloudCodeFunction("GetAccountDetails")]
public async Task<AccountDetailsResponse> GetAccountDetailsAsync(IExecutionContext ctx, IGameApiClient client)
{
return await _commerceService.GetAccountDetailsAsync(ctx, client);
}

[CloudCodeFunction("SetShippingAddress")]
public async Task<PrepareOrderResponse> SetShippingAddressAsync(IExecutionContext ctx, IGameApiClient client,
string contractId, string addressId)
{
return await _commerceService.SetShippingAddressAsync(ctx, client, contractId, addressId);
}

[CloudCodeFunction("PlaceOrder")]
public async Task<PlaceOrderResponse> PlaceOrderAsync(IExecutionContext ctx, IGameApiClient client, string contractId,
string tenderPlanId, string paymentType, string paymentId)
{
return await _commerceService.PlaceOrderAsync(ctx, client, contractId, tenderPlanId, paymentType, paymentId);
}

[CloudCodeFunction("PrepareOrder")]
public async Task<PrepareOrderResponse> PrepareOrderAsync(IExecutionContext ctx, IGameApiClient client,
List<OrderItemRequest> orderItems, string commOpId,
string correlationVectorId)
{
return await _commerceService.PrepareOrderAsync(ctx, client, orderItems, commOpId, correlationVectorId);
}
}
18 changes: 18 additions & 0 deletions Project/Contracts/AccountDetailsPayloadResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System.Collections.Generic;
using AutoMapper;

namespace Unity.WalmartAuthRelay.Contracts;

public class AccountDetailsPayloadResponse
{
public List<AddressResponse> Addresses { get; init; } = new ();
public PaymentsResponse Payments { get; init; } = null!;
}

public class AccountDetailsPayloadResponseProfile : Profile
{
public AccountDetailsPayloadResponseProfile()
{
CreateMap<Dto.WalmartIcs.AccountDetailsPayloadResponse, AccountDetailsPayloadResponse>();
}
}
25 changes: 25 additions & 0 deletions Project/Contracts/AccountDetailsResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System.Collections.Generic;
using AutoMapper;
using Unity.WalmartAuthRelay.Dto.WalmartIcs;

namespace Unity.WalmartAuthRelay.Contracts;

public class AccountDetailsResponse : CommonResponse<AccountDetailsPayloadResponse>
{
public AccountDetailsResponse(List<Error> errors, AccountDetailsPayloadResponse payload, Dictionary<string, string> headers)
: base(errors, payload, headers) { }

public AccountDetailsResponse()
: base([], new AccountDetailsPayloadResponse(), new Dictionary<string, string>()) { }
}

public class AccountDetailsResponseProfile : Profile
{
public AccountDetailsResponseProfile()
{
CreateMap<GetAccountDetailsResponse, AccountDetailsResponse>()
.ForMember(dest => dest.Errors, act => act.Ignore())
.ForMember(dest => dest.Payload, opt =>
opt.MapFrom(src => src.Payload));
}
}
22 changes: 22 additions & 0 deletions Project/Contracts/AddressResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using AutoMapper;
using Unity.WalmartAuthRelay.Dto.WalmartIcs;

namespace Unity.WalmartAuthRelay.Contracts;

public class AddressResponse
{
public Guid Id { get; init; }
public string FirstName { get; init; } = string.Empty;
public string LastName { get; init; } = string.Empty;
public string AddressLineOne { get; init; } = string.Empty;
public bool IsDefault { get; init; }
}

public class AddressResponseProfile : Profile
{
public AddressResponseProfile()
{
CreateMap<AccountDetailsAddressResponse, AddressResponse>();
}
}
17 changes: 17 additions & 0 deletions Project/Contracts/CommonResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;

namespace Unity.WalmartAuthRelay.Contracts;

public class CommonResponse<TPayload>
{
protected CommonResponse(List<Error> errors, TPayload payload, Dictionary<string, string> headers)
{
Errors = errors;
Payload = payload;
Headers = headers;
}

public List<Error> Errors { get; set; }
public TPayload Payload { get; set; }
public Dictionary<string, string> Headers { get; set; }
}
27 changes: 27 additions & 0 deletions Project/Contracts/CreditCardResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using System;
using System.Text.Json.Serialization;
using AutoMapper;
using Unity.WalmartAuthRelay.Dto.WalmartIcs;

namespace Unity.WalmartAuthRelay.Contracts;

public class CreditCardResponse
{
public Guid Id { get; init; }
public bool IsDefault { get; init; }

[JsonPropertyName("NeedVerifyCVV")]
public bool NeedVerifyCvv { get; init; }
public string PaymentType { get; init; } = string.Empty;
public string CardType { get; init; } = string.Empty;
public string LastFour { get; init; } = string.Empty;
public bool IsExpired { get; init; }
}

public class CreditCardResponseProfile : Profile
{
public CreditCardResponseProfile()
{
CreateMap<AccountDetailsCreditCardResponse, CreditCardResponse>();
}
}
19 changes: 19 additions & 0 deletions Project/Contracts/DeliveryAddressResponseField.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using AutoMapper;
using Unity.WalmartAuthRelay.Dto.WalmartIcs;

namespace Unity.WalmartAuthRelay.Contracts;

public class DeliveryAddressResponseField
{
public Guid Id { get; init; }
public string AddressLineOne { get; init; } = string.Empty;
}

public class DeliveryAddressProfile : Profile
{
public DeliveryAddressProfile()
{
CreateMap<AddressData, DeliveryAddressResponseField>();
}
}
Loading