Skip to content

Feature: Integrate GitHub Account Connection for Reown-Authenticated Users #21

@IgorShadurin

Description

@IgorShadurin

Overview

Implement GitHub account connection functionality for users who are authenticated via Reown. This will enable seamless integration between DappyKit and GitHub services while maintaining Web3 authentication flow.

Requirements

  • Maintain Reown as primary authentication method
  • Allow secure GitHub account linking
  • Handle OAuth flow with GitHub
  • Store and manage GitHub access tokens securely
  • Provide connection status and management UI

Technical Implementation

1. OAuth Configuration

  1. GitHub OAuth App Setup
interface GitHubOAuthConfig {
  clientId: string;
  clientSecret: string;
  redirectUri: string;
  scope: string[];
}

const githubOAuthConfig: GitHubOAuthConfig = {
  clientId: process.env.GITHUB_CLIENT_ID,
  clientSecret: process.env.GITHUB_CLIENT_SECRET,
  redirectUri: `${process.env.APP_URL}/api/auth/github/callback`,
  scope: ['read:user', 'user:email', 'repo']
};

2. Backend Implementation

  1. User Model Extension
interface UserGitHubConnection {
  githubId: string;
  accessToken: string;
  refreshToken: string;
  scope: string[];
  connectedAt: Date;
  lastUsed: Date;
}

interface User {
  // ... existing Reown user fields ...
  githubConnection?: UserGitHubConnection;
}
  1. Connection Controller
/**
 * Handles GitHub OAuth connection for Reown authenticated users
 */
class GitHubConnectionController {
  /**
   * Initiates GitHub OAuth flow
   * @param req - Express request with Reown user
   * @param res - Express response
   */
  async initiateConnection(req: Request, res: Response): Promise<void> {
    try {
      const { user } = req;
      const state = generateSecureState(user.id);
      
      const authUrl = new URL('https://github.com/login/oauth/authorize');
      authUrl.searchParams.append('client_id', githubOAuthConfig.clientId);
      authUrl.searchParams.append('redirect_uri', githubOAuthConfig.redirectUri);
      authUrl.searchParams.append('scope', githubOAuthConfig.scope.join(' '));
      authUrl.searchParams.append('state', state);
      
      res.redirect(authUrl.toString());
    } catch (error) {
      res.status(500).json({
        error: 'Failed to initiate GitHub connection'
      });
    }
  }

  /**
   * Handles OAuth callback and completes connection
   * @param req - Express request with OAuth code
   * @param res - Express response
   */
  async handleCallback(req: Request, res: Response): Promise<void> {
    try {
      const { code, state } = req.query;
      const userId = verifyAndExtractState(state as string);
      
      // Exchange code for tokens
      const tokens = await exchangeCodeForTokens(code as string);
      
      // Get GitHub user info
      const githubUser = await fetchGitHubUserInfo(tokens.access_token);
      
      // Store connection
      await this.storeGitHubConnection(userId, {
        githubId: githubUser.id,
        accessToken: tokens.access_token,
        refreshToken: tokens.refresh_token,
        scope: tokens.scope.split(','),
        connectedAt: new Date(),
        lastUsed: new Date()
      });
      
      res.redirect('/settings/connections');
    } catch (error) {
      res.status(500).json({
        error: 'Failed to complete GitHub connection'
      });
    }
  }
}
  1. Token Management
/**
 * Manages GitHub access tokens
 */
class GitHubTokenManager {
  /**
   * Refreshes GitHub access token
   * @param userId - Reown user ID
   * @returns Updated GitHub connection
   */
  async refreshToken(userId: string): Promise<UserGitHubConnection> {
    const user = await getUserById(userId);
    const { refreshToken } = user.githubConnection;
    
    const newTokens = await this.exchangeRefreshToken(refreshToken);
    const updatedConnection = {
      ...user.githubConnection,
      accessToken: newTokens.access_token,
      refreshToken: newTokens.refresh_token,
      lastUsed: new Date()
    };
    
    await this.updateGitHubConnection(userId, updatedConnection);
    return updatedConnection;
  }
}

3. Frontend Implementation

  1. Connection Component
/**
 * GitHub connection management component
 */
const GitHubConnection: React.FC = () => {
  const { user } = useReownAuth();
  const [isConnected, setIsConnected] = useState(false);
  
  useEffect(() => {
    setIsConnected(!!user?.githubConnection);
  }, [user]);
  
  const handleConnect = async () => {
    try {
      window.location.href = '/api/auth/github/connect';
    } catch (error) {
      console.error('Failed to initiate GitHub connection:', error);
    }
  };
  
  const handleDisconnect = async () => {
    try {
      await axios.post('/api/auth/github/disconnect');
      setIsConnected(false);
    } catch (error) {
      console.error('Failed to disconnect GitHub:', error);
    }
  };
  
  return (
    <div className="github-connection">
      <h2>GitHub Connection</h2>
      {isConnected ? (
        <div>
          <p>Connected to GitHub</p>
          <button onClick={handleDisconnect}>
            Disconnect GitHub
          </button>
        </div>
      ) : (
        <button onClick={handleConnect}>
          Connect GitHub Account
        </button>
      )}
    </div>
  );
};
  1. Connection Status Hook
/**
 * Custom hook for GitHub connection status
 */
const useGitHubConnection = () => {
  const { user } = useReownAuth();
  const [connectionStatus, setConnectionStatus] = useState({
    isConnected: false,
    lastUsed: null,
    scopes: []
  });
  
  useEffect(() => {
    if (user?.githubConnection) {
      setConnectionStatus({
        isConnected: true,
        lastUsed: user.githubConnection.lastUsed,
        scopes: user.githubConnection.scope
      });
    }
  }, [user]);
  
  return connectionStatus;
};

4. API Routes

  1. Connection Routes
router.get('/api/auth/github/connect', 
  authenticateReown, 
  githubController.initiateConnection
);

router.get('/api/auth/github/callback',
  authenticateReown,
  githubController.handleCallback
);

router.post('/api/auth/github/disconnect',
  authenticateReown,
  githubController.disconnectGitHub
);

5. Security Measures

  1. Token Encryption
/**
 * Encrypts GitHub tokens before storage
 */
const encryptToken = (token: string): string => {
  const cipher = crypto.createCipheriv(
    'aes-256-gcm',
    process.env.ENCRYPTION_KEY,
    process.env.ENCRYPTION_IV
  );
  return cipher.update(token, 'utf8', 'hex');
};
  1. State Validation
/**
 * Generates and validates OAuth state
 */
const generateSecureState = (userId: string): string => {
  const payload = {
    userId,
    timestamp: Date.now()
  };
  return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn: '5m' });
};

Database Schema Updates

-- Add GitHub connection table
CREATE TABLE user_github_connections (
  user_id VARCHAR(255) PRIMARY KEY,
  github_id VARCHAR(255) UNIQUE,
  access_token TEXT,
  refresh_token TEXT,
  scope TEXT[],
  connected_at TIMESTAMP,
  last_used TIMESTAMP,
  FOREIGN KEY (user_id) REFERENCES users(id)
);

-- Add indexes
CREATE INDEX idx_github_id ON user_github_connections(github_id);
CREATE INDEX idx_connected_at ON user_github_connections(connected_at);

Security Considerations

  1. Token Storage

    • Encrypt tokens at rest
    • Implement token rotation
    • Regular token validation
  2. Authentication Flow

    • Validate OAuth state
    • Implement PKCE
    • Rate limiting
  3. Scope Management

    • Minimal required permissions
    • Scope validation
    • Regular scope audits

Next Steps

  1. Review and approve technical design
  2. Set up GitHub OAuth application
  3. Implement backend services
  4. Create frontend components
  5. Deploy to staging
  6. Security audit
  7. Production deployment

Please review and provide feedback on this implementation plan.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions