···1+# Lanyard
2+3+A dedicated profile for researchers, built on the AT Protocol.
4+5+Researchers will use this as an alternative to the ORCID ID.
6+7+## Features
8+9+- **Account Creation**: Sign in with your Bluesky account using OAuth
10+- **Researcher Profile**: Mobile-first profile display with QR code sharing
11+- **Profile Management**: Manage honorifics, location, affiliations, and more
12+- **Social Networks**: Link to Twitter, LinkedIn, ResearchGate, Google Scholar, and Semble
13+- **Scholarly Contributions**: Add research using DOIs
14+- **Academic Events**: Track conference presentations and symposiums
15+16+## Technology Stack
17+18+- Next.js (latest)
19+- TypeScript
20+- Tailwind CSS v4
21+- AT Protocol (@atproto/*)
22+- Zod for validation
23+24+## Getting Started
25+26+### Quick Start (App Password)
27+28+1. Install dependencies:
29+ ```bash
30+ npm install
31+ ```
32+33+2. Set up your Bluesky app password:
34+ ```bash
35+ cp .env.example .env
36+ # Edit .env and add your BLUESKY_HANDLE and BLUESKY_APP_PASSWORD
37+ ```
38+39+3. Run the development server:
40+ ```bash
41+ npm run dev
42+ ```
43+44+4. Open [http://localhost:3000](http://localhost:3000)
45+46+See [SETUP.md](SETUP.md) for detailed setup instructions and OAuth configuration.
47+48+## Development
49+50+- `npm run dev` - Start development server
51+- `npm run build` - Build for production
52+- `npm run start` - Start production server
53+- `npm run lint` - Run ESLint
54+- `npm run format` - Format code with Prettier
···1+# Lanyard Setup Guide
2+3+## Quick Start with App Password (Recommended for Development)
4+5+### 1. Create a Bluesky App Password
6+7+1. Go to [https://bsky.app/settings/app-passwords](https://bsky.app/settings/app-passwords)
8+2. Click "Add App Password"
9+3. Give it a name (e.g., "Lanyard Development")
10+4. Copy the generated password
11+12+### 2. Configure Environment Variables
13+14+1. Copy the example environment file:
15+ ```bash
16+ cp .env.example .env
17+ ```
18+19+2. Edit `.env` and add your credentials:
20+ ```env
21+ # Set authentication method to app_password
22+ AUTH_METHOD=app_password
23+24+ # Add your Bluesky credentials
25+ BLUESKY_HANDLE=your-handle.bsky.social
26+ BLUESKY_APP_PASSWORD=your-app-password-here
27+28+ # Leave these as-is for now
29+ NEXT_PUBLIC_APP_URL=http://localhost:3000
30+ PDS_URL=https://bsky.social
31+ ```
32+33+### 3. Install Dependencies
34+35+```bash
36+npm install
37+```
38+39+### 4. Run the Development Server
40+41+```bash
42+npm run dev
43+```
44+45+Visit [http://localhost:3000](http://localhost:3000) and click "Sign In". You'll be automatically authenticated with your configured account.
46+47+## Authentication Methods
48+49+Lanyard supports two authentication methods:
50+51+### App Password (Development)
52+- **Pros**: Simple setup, no OAuth configuration needed
53+- **Cons**: Single account only, credentials in .env file
54+- **Use for**: Local development and testing
55+- **Configuration**: Set `AUTH_METHOD=app_password` in `.env`
56+57+### OAuth (Production)
58+- **Pros**: Multi-user support, secure authorization flow
59+- **Cons**: Requires OAuth client setup and configuration
60+- **Use for**: Production deployment
61+- **Configuration**: Set `AUTH_METHOD=oauth` in `.env`
62+63+## Switching Between Authentication Methods
64+65+Simply change the `AUTH_METHOD` value in your `.env` file:
66+67+```env
68+# For App Password
69+AUTH_METHOD=app_password
70+71+# For OAuth
72+AUTH_METHOD=oauth
73+```
74+75+Restart the development server after changing the method.
76+77+## Setting Up OAuth (Production)
78+79+Coming soon - OAuth setup requires AT Protocol OAuth client configuration.
80+81+## Troubleshooting
82+83+### "App password not configured" error
84+- Check that `BLUESKY_HANDLE` and `BLUESKY_APP_PASSWORD` are set in `.env`
85+- Ensure there are no extra spaces or quotes around the values
86+- Restart the development server after changing `.env`
87+88+### Authentication fails
89+- Verify your app password is correct
90+- Make sure your Bluesky account is active
91+- Check that `PDS_URL` is set to `https://bsky.social`
92+93+### Can't access dashboard
94+- Make sure you're signed in (check for session cookie)
95+- Try clearing cookies and signing in again
96+- Check the browser console for errors
97+98+## Next Steps
99+100+Once authenticated, you can:
101+1. View your dashboard at `/dashboard`
102+2. Add affiliations, publications, and events
103+3. Configure your profile settings
104+4. View your public profile at `/{your-handle}`
···1+/**
2+ * OAuth Client Placeholder
3+ *
4+ * The @atproto/oauth-client-node package has a complex setup that requires:
5+ * - Proper client metadata configuration
6+ * - State and session stores
7+ * - DPoP (Demonstrated Proof of Possession) setup
8+ *
9+ * For now, this is a placeholder. To implement properly:
10+ * 1. Follow the AT Protocol OAuth documentation
11+ * 2. Set up proper session and state storage
12+ * 3. Configure client metadata correctly
13+ *
14+ * Alternative: Use direct API authentication instead of OAuth for simpler setup
15+ */
16+17+export async function getOAuthClient() {
18+ throw new Error(
19+ 'OAuth client not yet fully implemented. Please configure OAuth according to AT Protocol documentation.'
20+ );
21+}
22+23+export async function createAuthUrl(_handle: string): Promise<string> {
24+ throw new Error(
25+ 'OAuth client not yet fully implemented. Please configure OAuth according to AT Protocol documentation.'
26+ );
27+}
+46
src/lib/auth/oauth-client.ts
···0000000000000000000000000000000000000000000000
···1+/**
2+ * OAuth Client Configuration
3+ *
4+ * Note: The @atproto/oauth-client-node package requires complex setup.
5+ * This is a temporary type-safe placeholder implementation.
6+ *
7+ * To properly implement OAuth:
8+ * 1. Review AT Protocol OAuth documentation
9+ * 2. Set up NodeSavedStateStore and NodeSavedSessionStore
10+ * 3. Configure client metadata properly
11+ * 4. Handle DPoP tokens
12+ *
13+ * For initial development, consider using app passwords or direct API auth
14+ */
15+16+// Placeholder type
17+export type OAuthClient = {
18+ authorize: (handle: string) => Promise<string>;
19+ callback: (params: URLSearchParams) => Promise<{
20+ session: {
21+ sub: string;
22+ scope: string;
23+ };
24+ state: string | null;
25+ }>;
26+};
27+28+let oauthClient: OAuthClient | null = null;
29+30+export async function getOAuthClient(): Promise<OAuthClient> {
31+ if (oauthClient) {
32+ return oauthClient;
33+ }
34+35+ // TODO: Implement proper OAuth client initialization
36+ // See @atproto/oauth-client-node documentation
37+38+ throw new Error(
39+ 'OAuth client not yet fully configured. Please set up OAuth according to AT Protocol documentation.'
40+ );
41+}
42+43+export async function createAuthUrl(handle: string): Promise<string> {
44+ const client = await getOAuthClient();
45+ return await client.authorize(handle);
46+}
···1+/**
2+ * Type exports for Lanyard application
3+ * Re-exports generated AT Protocol lexicon types with convenient aliases
4+ */
5+6+import type {
7+ AtLanyardResearcher,
8+ AtLanyardWork,
9+ AtLanyardEvent,
10+ AtLanyardLink,
11+ AtLanyardOrganization,
12+ AtLanyardPublication,
13+ AtLanyardLocation,
14+} from './generated';
15+16+// Main record types
17+export type Researcher = AtLanyardResearcher.Record;
18+export type Work = AtLanyardWork.Record;
19+export type Event = AtLanyardEvent.Record;
20+export type Link = AtLanyardLink.Record;
21+export type Organization = AtLanyardOrganization.Main;
22+export type Publication = AtLanyardPublication.Main;
23+export type Location = AtLanyardLocation.Main;
24+25+// Nested types
26+export type Affiliation = AtLanyardResearcher.Affiliation;
27+28+// Convenience type aliases for enums and unions
29+export type Honorific = 'Dr' | 'Prof';
30+export type WorkType = Work['type'];
31+export type EventType = Event['type'];
32+export type LinkType = Link['type'];
33+export type LinkPlatform = NonNullable<Link['platform']>;
34+export type OrganizationType = NonNullable<Organization['type']>;
35+export type PublicationType = NonNullable<Publication['type']>;
+5
src/types/multiformats.d.ts
···00000
···1+// Type declarations for multiformats package
2+// This resolves import issues with the generated AT Protocol types
3+declare module 'multiformats/cid' {
4+ export { CID } from 'multiformats';
5+}