feat: New account system with improved team management (#273)

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 comprehensive account management with combined user and
team info.
  - Added advanced, context-aware logging utilities.
- Implemented invite code generation for teams with uniqueness
guarantees.
- Expanded example data for users, teams, subscriptions, sessions, and
games.

- **Enhancements**
- Refined user, team, member, and Steam account schemas for richer data
and validation.
  - Streamlined user creation, login acknowledgment, and error handling.
  - Improved API authentication and unified actor context management.
- Added persistent shared temporary volume support to API and auth
services.
- Enhanced Steam account management with create, update, and event
notifications.
- Refined team listing and serialization integrating Steam accounts as
members.
  - Simplified event, context, and logging systems.
- Updated API and auth middleware for better token handling and actor
provisioning.

- **Bug Fixes**
  - Fixed multiline log output to prefix each line with log level.

- **Removals**
- Removed machine and subscription management features, including
schemas and DB tables.
- Disabled machine-based authentication and removed related subject
schemas.
- Removed deprecated fields and legacy logic from member and team
management.
- Removed legacy event and error handling related to teams and members.

- **Chores**
  - Reorganized and cleaned exports across utility and API modules.
- Updated database schemas for users, teams, members, and Steam
accounts.
  - Improved internal code structure, imports, and error messaging.
- Moved logger patching to earlier initialization for consistent
logging.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Wanjohi
2025-05-06 07:26:59 +03:00
committed by GitHub
parent a0dc353561
commit 70d629227a
39 changed files with 1194 additions and 1480 deletions

View File

@@ -1,40 +1,22 @@
import { Resource } from "sst"
import { type Env } from "hono";
import { PasswordUI } from "./ui";
import { logger } from "hono/logger";
import { subjects } from "../subjects"
import { Select, PasswordUI } from "./ui";
import { issuer } from "@openauthjs/openauth";
import { User } from "@nestri/core/user/index"
// import { Email } from "@nestri/core/email/index";
// import { Machine } from "@nestri/core/machine/index"
import { Email } from "@nestri/core/email/index";
import { patchLogger } from "../utils/patch-logger";
import { handleDiscord, handleGithub } from "./utils";
import { MemoryStorage } from "@openauthjs/openauth/storage/memory";
// import { type Provider } from "@openauthjs/openauth/provider/provider"
import { DiscordAdapter, PasswordAdapter, GithubAdapter } from "./adapters";
type OauthUser = {
primary: {
email: any;
primary: any;
verified: any;
};
avatar: any;
username: any;
}
console.log("STORAGE", process.env.STORAGE)
patchLogger();
const app = issuer({
select: Select({
providers: {
machine: {
hide: true
}
}
}),
//TODO: Create our own Storage
//TODO: Create our own Storage (?)
storage: MemoryStorage({
persist: process.env.STORAGE //"/tmp/persist.json",
persist: process.env.STORAGE
}),
theme: {
title: "Nestri | Auth",
@@ -67,35 +49,20 @@ const app = issuer({
password: PasswordAdapter(
PasswordUI({
sendCode: async (email, code) => {
console.log("email & code:", email, code)
// await Email.send(
// "auth",
// email,
// `Nestri code: ${code}`,
// `Your Nestri login code is ${code}`,
// )
// Do not debug show code in production
if (Resource.App.stage != "production") {
console.log("email & code:", email, code)
}
await Email.send(
"auth",
email,
`Nestri code: ${code}`,
`Your Nestri login code is ${code}`,
)
},
}),
),
// machine: {
// type: "machine",
// async client(input) {
// // FIXME: Do we really need this?
// // if (input.clientSecret !== Resource.AuthFingerprintKey.value) {
// // throw new Error("Invalid authorization token");
// // }
// const fingerprint = input.params.fingerprint;
// if (!fingerprint) {
// throw new Error("Hostname is required");
// }
// return {
// fingerprint,
// };
// },
// init() { }
// } as Provider<{ fingerprint: string; }>,
},
allow: async (input) => {
const url = new URL(input.redirectURI);
@@ -105,48 +72,6 @@ const app = issuer({
return false;
},
success: async (ctx, value, req) => {
// I dunno what i broke... will check later
// if (value.provider === "machine") {
// const countryCode = req.headers.get('CloudFront-Viewer-Country') || 'Unknown'
// const country = req.headers.get('CloudFront-Viewer-Country-Name') || 'Unknown'
// const latitude = Number(req.headers.get('CloudFront-Viewer-Latitude')) || 0
// const longitude = Number(req.headers.get('CloudFront-Viewer-Longitude')) || 0
// const timezone = req.headers.get('CloudFront-Viewer-Time-Zone') || 'Unknown'
// const fingerprint = value.fingerprint
// const existing = await Machine.fromFingerprint(fingerprint)
// if (!existing) {
// const machineID = await Machine.create({
// countryCode,
// country,
// fingerprint,
// timezone,
// location: {
// latitude,
// longitude
// },
// //FIXME: Make this better
// // userID: null
// })
// return ctx.subject("machine", {
// machineID,
// fingerprint
// });
// }
// return ctx.subject("machine", {
// machineID: existing.id,
// fingerprint
// });
// }
// TODO: This works, so use this while registering the task
// console.log("country_code", req.headers.get('CloudFront-Viewer-Country'))
// console.log("country_name", req.headers.get('CloudFront-Viewer-Country-Name'))
// console.log("latitude", req.headers.get('CloudFront-Viewer-Latitude'))
// console.log("longitude", req.headers.get('CloudFront-Viewer-Longitude'))
// console.log("timezone", req.headers.get('CloudFront-Viewer-Time-Zone'))
if (value.provider === "password") {
const email = value.email
const username = value.username
@@ -165,21 +90,23 @@ const app = issuer({
userID,
email
}, {
subject: email
subject: userID
});
} else if (matching) {
await User.acknowledgeLogin(matching.id)
//Sign In
return ctx.subject("user", {
userID: matching.id,
email
}, {
subject: email
subject: matching.id
});
}
}
let user = undefined as OauthUser | undefined;
let user;
if (value.provider === "github") {
const access = value.tokenset.access;
@@ -200,7 +127,7 @@ const app = issuer({
const userID = await User.create({
email: user.primary.email,
name: user.username,
avatarUrl: user.avatar
avatarUrl: user.avatar,
});
if (!userID) throw new Error("Error creating user");
@@ -209,15 +136,17 @@ const app = issuer({
userID,
email: user.primary.email
}, {
subject: user.primary.email
subject: userID
});
} else {
await User.acknowledgeLogin(matching.id)
//Sign In
return await ctx.subject("user", {
userID: matching.id,
email: user.primary.email
}, {
subject: user.primary.email
subject: matching.id
});
}
@@ -231,13 +160,12 @@ const app = issuer({
},
}).use(logger())
patchLogger();
export default {
port: 3002,
idleTimeout: 255,
fetch: (req: Request) =>
app.fetch(req, undefined, {
fetch: (req: Request, env: Env) =>
app.fetch(req, env, {
waitUntil: (fn) => fn,
passThroughOnException: () => { },
}),

View File

@@ -14,7 +14,7 @@ export const handleDiscord = async (accessKey: string) => {
}
const user = await response.json();
// console.log("raw user", user)
if (!user.verified) {
throw new Error("Email not verified");
}
@@ -28,7 +28,7 @@ export const handleDiscord = async (accessKey: string) => {
avatar: user.avatar
? `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png`
: null,
username: user.global_name ?? user.username
username: user.global_name ?? user.username,
};
} catch (error) {
console.error('Discord OAuth error:', error);

View File

@@ -1,8 +1,6 @@
import fetch from "node-fetch";
export const handleGithub = async (accessKey: string) => {
console.log("acceskey", accessKey)
const headers = {
Authorization: `token ${accessKey}`,
Accept: "application/vnd.github.v3+json",
@@ -33,7 +31,7 @@ export const handleGithub = async (accessKey: string) => {
return {
primary: { email, primary, verified },
avatar: user.avatar_url,
username: user.name ?? user.login
username: user.name ?? user.login,
};
} catch (error) {
console.error('GitHub OAuth error:', error);