Highly ambitious ATProtocol AppView service and sdks
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

add waitlist check to cli

+130
+15
packages/cli/src/auth/device_flow.ts
··· 1 1 import { DeviceFlowClient, type OAuthUserInfo } from "@slices/oauth"; 2 2 import { logger } from "../utils/logger.ts"; 3 3 import { ConfigManager } from "./config.ts"; 4 + import { checkUserWaitlistAccess, showWaitlistError } from "../utils/waitlist.ts"; 4 5 5 6 const DEFAULT_AIP_BASE_URL = "https://auth.slices.network"; 6 7 const DEFAULT_CLIENT_ID = "24e77d48-a892-4043-b113-ea241f339397"; ··· 40 41 ); 41 42 42 43 const userInfo: OAuthUserInfo = await deviceClient.getUserInfo(tokenResponse.access_token); 44 + 45 + if (!userInfo.did) { 46 + throw new Error("Failed to retrieve user DID from authentication response."); 47 + } 48 + 49 + // Only check waitlist access for production Slices network 50 + if (aipBaseUrl === "https://auth.slices.network") { 51 + const { hasAccess, isOnWaitlist } = await checkUserWaitlistAccess(userInfo.did); 52 + 53 + if (!hasAccess) { 54 + showWaitlistError(userInfo.did, isOnWaitlist); 55 + throw new Error("Access denied: User is not on the invite list"); 56 + } 57 + } 43 58 44 59 const expiresAt = tokenResponse.expires_in 45 60 ? Date.now() + (tokenResponse.expires_in * 1000)
+16
packages/cli/src/utils/client.ts
··· 1 1 import type { AuthProvider } from "@slices/client"; 2 2 import { AtProtoClient } from "../generated_client.ts"; 3 3 import { ConfigManager } from "../auth/config.ts"; 4 + import { checkUserWaitlistAccess, showWaitlistError } from "./waitlist.ts"; 4 5 5 6 class DeviceAuthProvider implements AuthProvider { 6 7 private config: ConfigManager; ··· 42 43 throw new Error("Not authenticated. Run 'slices login' first."); 43 44 } 44 45 46 + const authConfig = config.get().auth!; 47 + 48 + if (!authConfig.did) { 49 + throw new Error("Missing user DID in authentication config. Please re-authenticate using 'slices login'."); 50 + } 51 + 52 + // Only check waitlist access for production Slices network 53 + if (apiUrl === "https://api.slices.network") { 54 + const { hasAccess, isOnWaitlist } = await checkUserWaitlistAccess(authConfig.did, apiUrl); 55 + 56 + if (!hasAccess) { 57 + showWaitlistError(authConfig.did, isOnWaitlist); 58 + Deno.exit(1); 59 + } 60 + } 45 61 46 62 // Create simple auth provider that uses stored device flow tokens 47 63 const authProvider = new DeviceAuthProvider(config);
+99
packages/cli/src/utils/waitlist.ts
··· 1 + import { AtProtoClient } from "../generated_client.ts"; 2 + import { logger } from "./logger.ts"; 3 + 4 + const DEFAULT_SLICE_URI = 5 + "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhd4jhrd2z"; 6 + const DEFAULT_ADMIN_DID = "did:plc:bcgltzqazw5tb6k2g3ttenbj"; 7 + 8 + function getSliceUri(): string { 9 + return Deno.env.get("SLICE_URI") || DEFAULT_SLICE_URI; 10 + } 11 + 12 + function getAdminDid(): string { 13 + return Deno.env.get("ADMIN_DID") || DEFAULT_ADMIN_DID; 14 + } 15 + 16 + export interface WaitlistCheckResult { 17 + hasAccess: boolean; 18 + isOnWaitlist: boolean; 19 + } 20 + 21 + export async function checkUserWaitlistAccess( 22 + userDid: string, 23 + apiUrl = "https://api.slices.network" 24 + ): Promise<WaitlistCheckResult> { 25 + try { 26 + const sliceUri = getSliceUri(); 27 + const adminDid = getAdminDid(); 28 + 29 + // Create a public client for checking waitlist status (no auth needed) 30 + const client = new AtProtoClient(apiUrl, sliceUri); 31 + 32 + // Query for invites for this DID - using json field to query the record content 33 + const invitesResult = 34 + await client.network.slices.waitlist.invite.getRecords({ 35 + where: { 36 + did: { eq: adminDid }, 37 + slice: { eq: sliceUri }, 38 + json: { contains: userDid }, 39 + }, 40 + limit: 1, 41 + }); 42 + 43 + // Check if user has a valid invite 44 + if (invitesResult.records && invitesResult.records.length > 0) { 45 + const invite = invitesResult.records[0]; 46 + 47 + // Check if invite has expired 48 + if (invite.value.expiresAt) { 49 + const expiresAt = new Date(invite.value.expiresAt); 50 + const now = new Date(); 51 + if (expiresAt < now) { 52 + return { hasAccess: false, isOnWaitlist: false }; // Invite has expired 53 + } 54 + } 55 + 56 + return { hasAccess: true, isOnWaitlist: false }; // Valid invite found 57 + } 58 + 59 + // Check if user is already on the waitlist - requests are created by the user so record.did is correct 60 + const requestsResult = 61 + await client.network.slices.waitlist.request.getRecords({ 62 + where: { 63 + slice: { eq: sliceUri }, 64 + json: { eq: userDid }, 65 + }, 66 + limit: 1, 67 + }); 68 + 69 + const isOnWaitlist = 70 + requestsResult.records && requestsResult.records.length > 0; 71 + 72 + return { hasAccess: false, isOnWaitlist }; 73 + } catch (error) { 74 + logger.error("Error checking user waitlist access:", error); 75 + return { hasAccess: false, isOnWaitlist: false }; // Default to blocking access on error 76 + } 77 + } 78 + 79 + export function showWaitlistError( 80 + userDid: string, 81 + isOnWaitlist: boolean 82 + ): void { 83 + console.error("\n❌ Access Denied"); 84 + console.error("─".repeat(50)); 85 + 86 + if (isOnWaitlist) { 87 + console.error("You are already on the waitlist for Slices."); 88 + console.error("Please wait for an invitation to be sent to your account."); 89 + } else { 90 + console.error("You need an invitation to use Slices."); 91 + console.error("Please visit https://slices.network to request access."); 92 + } 93 + 94 + console.error(`\nYour DID: ${userDid}`); 95 + console.error("─".repeat(50)); 96 + console.error( 97 + "If you believe this is an error, please DM @slices.network on Bsky.\n" 98 + ); 99 + }