feat: Add Steam account linking with team creation (#274)

## Description
<!-- Briefly describe the purpose and scope of your changes -->


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Introduced a real-time Steam login flow using QR codes and server-sent
events (SSE) for team creation and authentication.
- Added Steam account and friend management, including secure credential
storage and friend list synchronization.
- Integrated Steam login endpoints into the API, enabling QR code-based
login and automated team setup.

- **Improvements**
- Enhanced data security by implementing encrypted storage for sensitive
tokens.
- Updated database schema to support Steam accounts, teams, memberships,
and social connections.
- Refined type definitions and consolidated account-related information
for improved consistency.

- **Bug Fixes**
  - Fixed trade ban status representation for Steam accounts.

- **Chores**
- Removed legacy C# Steam authentication service and related
configuration files.
  - Updated and cleaned up package dependencies and development tooling.
  - Streamlined type declaration files and resource definitions.

- **Style**
- Redesigned the team creation page UI with a modern, animated QR code
login interface.

- **Documentation**
  - Updated OpenAPI documentation for new Steam login endpoints.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wanjohi
2025-05-09 01:13:44 +03:00
committed by GitHub
parent 70d629227a
commit 7e69af977b
51 changed files with 2332 additions and 2805 deletions

View File

@@ -2,6 +2,7 @@ import { ulid } from "ulid";
export const prefixes = {
user: "usr",
credentials:"crd",
team: "tem",
product: "prd",
session: "ses",

View File

@@ -1,4 +1,5 @@
export * from "./fn"
export * from "./log"
export * from "./id"
export * from "./invite"
export * from "./invite"
export * from "./token"

View File

@@ -0,0 +1,58 @@
import { z } from 'zod';
import { fn } from './fn';
import crypto from 'crypto';
import { Resource } from 'sst';
// This is a 32-character random ASCII string
const rawKey = Resource.SteamEncryptionKey.value;
// Turn it into exactly 32 bytes via UTF-8
const key = Buffer.from(rawKey, 'utf8');
if (key.length !== 32) {
throw new Error(
`SteamEncryptionKey must be exactly 32 bytes; got ${key.length}`
);
}
const ENCRYPTION_IV_LENGTH = 12; // 96 bits for GCM
export namespace Token {
export const encrypt = fn(
z.string().min(4),
(token) => {
const iv = crypto.randomBytes(ENCRYPTION_IV_LENGTH);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const ciphertext = Buffer.concat([
cipher.update(token, 'utf8'),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return ['v1', iv.toString('hex'), tag.toString('hex'), ciphertext.toString('hex')].join(':');
});
export const decrypt = fn(
z.string().min(4),
(data) => {
const [version, ivHex, tagHex, ciphertextHex] = data.split(':');
if (version !== 'v1' || !ivHex || !tagHex || !ciphertextHex) {
throw new Error('Invalid token format');
}
const iv = Buffer.from(ivHex, 'hex');
const tag = Buffer.from(tagHex, 'hex');
const ciphertext = Buffer.from(ciphertextHex, 'hex');
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
const plaintext = Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]);
return plaintext.toString('utf8');
});
}