Functions for validating GitHub tokens for integration with GitHub repositories.
This module provides the following functionality:
/**
* Checks if a value matches GitHub token format
* @param value - Token string to check
* @returns true if the value matches a valid GitHub token format
*/
function isGithubToken(value: string): boolean;
/**
* Validates GitHub token format and returns result with error message
* @param value - Token string to validate
* @returns Tuple of [isValid, errorMessage]
*/
function validateGithubToken(value: string): [boolean, string | undefined];import { isGithubToken, validateGithubToken } from '@lightdash/common';
// Check if token format is valid
if (isGithubToken('ghp_1234567890abcdef')) {
console.log('Valid GitHub token format');
}
// Check different token types
console.log(isGithubToken('ghp_abc123')); // true - Personal access token
console.log(isGithubToken('github_pat_xyz')); // true - Fine-grained token
console.log(isGithubToken('ghs_token123')); // true - Server token
console.log(isGithubToken('invalid_token')); // false
// Validate with error message
const [isValid, errorMessage] = validateGithubToken('invalid_token');
if (!isValid) {
console.error(errorMessage);
// "GitHub token should start with "github_pat_" or "ghp_""
}import { validateGithubToken } from '@lightdash/common';
function validateGitHubConnectionForm(formData: {
repoUrl: string;
token: string;
}): string | null {
// Validate token format
const [isValid, error] = validateGithubToken(formData.token);
if (!isValid) {
return error || 'Invalid GitHub token';
}
return null; // No errors
}
// Usage
const error = validateGitHubConnectionForm({
repoUrl: 'https://github.com/user/repo',
token: 'ghp_mytoken123',
});
if (error) {
showError(error);
}import { validateGithubToken } from '@lightdash/common';
// Validate GitHub token in API request
app.post('/api/integrations/github', async (req, res) => {
const { repositoryUrl, accessToken } = req.body;
// Validate token format
const [isValid, errorMessage] = validateGithubToken(accessToken);
if (!isValid) {
return res.status(400).json({
error: 'Invalid GitHub token',
details: errorMessage,
});
}
try {
// Connect to GitHub with validated token
await connectGitHubRepository(repositoryUrl, accessToken);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});import { isGithubToken } from '@lightdash/common';
async function testGitHubConnection(token: string, repoUrl: string): Promise<boolean> {
// First validate token format
if (!isGithubToken(token)) {
throw new Error('Invalid GitHub token format. Token should start with "ghp_", "github_pat_", or "ghs_"');
}
// Then test actual connection
try {
const response = await fetch(repoUrl, {
headers: {
Authorization: `token ${token}`,
},
});
return response.ok;
} catch (error) {
console.error('GitHub connection failed:', error);
return false;
}
}
// Usage
const canConnect = await testGitHubConnection(
'ghp_mytoken',
'https://api.github.com/repos/user/repo'
);import { validateGithubToken } from '@lightdash/common';
interface GitHubDbtConnection {
type: 'github';
repository: string;
branch: string;
personalAccessToken: string;
projectPath: string;
}
function validateDbtGitHubConnection(
connection: GitHubDbtConnection
): string[] {
const errors: string[] = [];
// Validate required fields
if (!connection.repository) {
errors.push('Repository URL is required');
}
if (!connection.personalAccessToken) {
errors.push('Personal access token is required');
} else {
// Validate token format
const [isValid, errorMessage] = validateGithubToken(
connection.personalAccessToken
);
if (!isValid) {
errors.push(errorMessage || 'Invalid token format');
}
}
if (!connection.branch) {
errors.push('Branch name is required');
}
return errors;
}
// Usage
const connection: GitHubDbtConnection = {
type: 'github',
repository: 'https://github.com/myorg/dbt-project',
branch: 'main',
personalAccessToken: 'ghp_abc123',
projectPath: '/dbt',
};
const errors = validateDbtGitHubConnection(connection);
if (errors.length > 0) {
console.error('Connection validation failed:', errors);
}import { validateGithubToken } from '@lightdash/common';
function GitHubSettingsForm() {
const [token, setToken] = useState('');
const [error, setError] = useState<string | null>(null);
const handleTokenChange = (newToken: string) => {
setToken(newToken);
// Validate on change
if (newToken) {
const [isValid, errorMessage] = validateGithubToken(newToken);
setError(isValid ? null : errorMessage || 'Invalid token');
} else {
setError(null);
}
};
const handleSubmit = async () => {
// Final validation before submit
const [isValid, errorMessage] = validateGithubToken(token);
if (!isValid) {
setError(errorMessage || 'Invalid token');
return;
}
// Save token
await saveGitHubToken(token);
};
return (
<form onSubmit={handleSubmit}>
<input
type="password"
value={token}
onChange={(e) => handleTokenChange(e.target.value)}
placeholder="ghp_..."
/>
{error && <div className="error">{error}</div>}
<button type="submit" disabled={!!error}>
Save
</button>
</form>
);
}import { isGithubToken } from '@lightdash/common';
async function migrateGitHubTokens() {
const projects = await db.projects.findMany({
where: {
dbtConnectionType: 'github',
},
});
for (const project of projects) {
const token = project.dbtConnection?.personalAccessToken;
if (token && !isGithubToken(token)) {
console.warn(
`Project ${project.id} has invalid GitHub token format. Please update.`
);
}
}
}import { validateGithubToken } from '@lightdash/common';
function validateEnvironmentVariables() {
const githubToken = process.env.GITHUB_TOKEN;
if (githubToken) {
const [isValid, error] = validateGithubToken(githubToken);
if (!isValid) {
console.error('Invalid GITHUB_TOKEN environment variable:', error);
process.exit(1);
}
console.log('GitHub token validated successfully');
}
}
// Run on startup
validateEnvironmentVariables();import { validateGithubToken } from '@lightdash/common';
async function connectGitHub(options: { token: string; repo: string }) {
// Validate token format
const [isValid, errorMessage] = validateGithubToken(options.token);
if (!isValid) {
console.error('Error:', errorMessage);
console.log('GitHub tokens should start with:');
console.log(' - ghp_ (Personal access tokens)');
console.log(' - github_pat_ (Fine-grained personal access tokens)');
console.log(' - ghs_ (Server tokens)');
process.exit(1);
}
console.log('Token format is valid');
// Continue with connection
await setupGitHubIntegration(options);
}import { isGithubToken, validateGithubToken } from '@lightdash/common';
describe('GitHub token validation', () => {
describe('isGithubToken', () => {
it('should accept ghp_ prefix', () => {
expect(isGithubToken('ghp_1234567890abcdef')).toBe(true);
});
it('should accept github_pat_ prefix', () => {
expect(isGithubToken('github_pat_abc123')).toBe(true);
});
it('should accept ghs_ prefix', () => {
expect(isGithubToken('ghs_token123')).toBe(true);
});
it('should reject invalid prefix', () => {
expect(isGithubToken('invalid_token')).toBe(false);
});
it('should reject empty string', () => {
expect(isGithubToken('')).toBe(false);
});
});
describe('validateGithubToken', () => {
it('should return true for valid token', () => {
const [isValid, error] = validateGithubToken('ghp_abc123');
expect(isValid).toBe(true);
expect(error).toBeUndefined();
});
it('should return error for invalid token', () => {
const [isValid, error] = validateGithubToken('invalid');
expect(isValid).toBe(false);
expect(error).toContain('should start with');
});
});
});GitHub uses different token prefixes for different token types:
ghp_: Personal access tokens (classic)github_pat_: Fine-grained personal access tokensghs_: Server-to-server tokensAll of these are considered valid by the validation functions.