Skip to content

Commit 163c29c

Browse files
authored
Add GitHub OAuth sign-in flow (#22)
* add github oauth device flow scaffolding * switch github api auth to oauth sessions * replace token setup with github sign in * update auth errors and disconnect handling * refresh tests and docs for oauth flow * fix popup oauth device flow resume * improve onboarding code copy * move watched repos to local storage Chrome sync storage has an 8KB per-item limit which we were hitting with larger repo lists. This moves watchedRepos to local storage (unlimited quota) and adds a one-time migration for existing installs. Also added import validation to enforce the 50 repo limit (unless unlimited mode is enabled) and better error handling around the import flow. * fix validation follow-up for oauth branch * keep ci checks focused on pull requests * ci: split pull request checks by job --------- Co-authored-by: jonmartin721 <jonmartin721@users.noreply.github.com>
1 parent 2632042 commit 163c29c

40 files changed

Lines changed: 1795 additions & 983 deletions

.github/workflows/ci.yml

Lines changed: 65 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,79 @@ on:
77
branches: [ main, master ]
88

99
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v6
15+
16+
- name: Setup Node.js
17+
uses: actions/setup-node@v6
18+
with:
19+
node-version: '18'
20+
cache: 'npm'
21+
22+
- name: Install dependencies
23+
run: npm ci
24+
25+
- name: Run linter
26+
run: npm run lint
27+
28+
typecheck:
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- uses: actions/checkout@v6
33+
34+
- name: Setup Node.js
35+
uses: actions/setup-node@v6
36+
with:
37+
node-version: '18'
38+
cache: 'npm'
39+
40+
- name: Install dependencies
41+
run: npm ci
42+
43+
- name: Run type checker
44+
run: npm run typecheck
45+
1046
test:
1147
runs-on: ubuntu-latest
1248

1349
steps:
14-
- uses: actions/checkout@v6
50+
- uses: actions/checkout@v6
1551

16-
- name: Setup Node.js
17-
uses: actions/setup-node@v6
18-
with:
19-
node-version: '18'
20-
cache: 'npm'
52+
- name: Setup Node.js
53+
uses: actions/setup-node@v6
54+
with:
55+
node-version: '18'
56+
cache: 'npm'
2157

22-
- name: Install dependencies
23-
run: npm ci
58+
- name: Install dependencies
59+
run: npm ci
2460

25-
- name: Run linter
26-
run: npm run lint
61+
- name: Run tests with coverage
62+
run: npm test -- --coverage --coverageReporters=text --coverageReporters=lcov
2763

28-
- name: Run type checker
29-
run: npm run typecheck
64+
- name: Upload coverage reports to Codecov
65+
uses: codecov/codecov-action@v5
66+
with:
67+
token: ${{ secrets.CODECOV_TOKEN }}
68+
69+
build:
70+
runs-on: ubuntu-latest
71+
72+
steps:
73+
- uses: actions/checkout@v6
3074

31-
- name: Run tests with coverage
32-
run: npm test -- --coverage --coverageReporters=text --coverageReporters=lcov
75+
- name: Setup Node.js
76+
uses: actions/setup-node@v6
77+
with:
78+
node-version: '18'
79+
cache: 'npm'
3380

34-
- name: Upload coverage reports to Codecov
35-
uses: codecov/codecov-action@v5
36-
with:
37-
token: ${{ secrets.CODECOV_TOKEN }}
81+
- name: Install dependencies
82+
run: npm ci
3883

39-
- name: Validate extension build
40-
run: npm run build
84+
- name: Validate extension build
85+
run: npm run build

PRIVACY.md

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ GitHub Devwatch is a Chrome extension for monitoring activity on GitHub reposito
1212

1313
GitHub Devwatch collects and stores the following data **locally on your device only**:
1414

15-
1. **GitHub Personal Access Token**
15+
1. **GitHub OAuth Session**
16+
- Created when you connect GitHub through the built-in device-flow sign-in
1617
- Stored by the extension in Chrome storage
17-
- Current builds encrypt the token before writing it to local storage and keep a decrypted session copy while the extension is running
18+
- Current builds encrypt the auth session before writing it to local storage and keep a decrypted session copy while the extension is running
1819
- Used only to authenticate with GitHub's API
1920
- Not sent to third-party services operated by this project
2021
- Never shared with anyone
@@ -46,16 +47,16 @@ GitHub Devwatch collects and stores the following data **locally on your device
4647

4748
All data collected is used exclusively to provide the extension's functionality:
4849

49-
- Your GitHub token authenticates API requests to GitHub
50+
- Your GitHub connection authenticates API requests to GitHub
5051
- Your repository list determines which repositories to monitor
5152
- Your settings customize how the extension behaves
5253
- Activity data is displayed in the extension popup for your review
5354

5455
## Data Storage
5556

56-
- The extension uses Chrome storage APIs for settings, cached activity, and token handling
57+
- The extension uses Chrome storage APIs for settings, cached activity, and GitHub sign-in handling
5758
- Settings and repository lists can optionally sync across your Chrome browsers if you use Chrome Sync
58-
- Token handling uses local and session storage rather than Chrome sync
59+
- GitHub sign-in data uses local and session storage rather than Chrome sync
5960
- You can clear all data at any time by uninstalling the extension or using Chrome's "Clear extension data" feature
6061

6162
## Third-Party Services
@@ -65,7 +66,7 @@ All data collected is used exclusively to provide the extension's functionality:
6566
This extension communicates with GitHub's API (api.github.com) to fetch repository activity. When you use this extension:
6667

6768
- API requests are made directly from your browser to GitHub
68-
- Requests include your GitHub Personal Access Token for authentication
69+
- Requests include your GitHub OAuth access token for authentication
6970
- GitHub's privacy policy and terms of service apply to these interactions
7071
- See GitHub's privacy policy at: https://docs.github.com/en/site-policy/privacy-policies/github-privacy-statement
7172

@@ -90,7 +91,7 @@ GitHub Devwatch does **NOT**:
9091

9192
The extension requests the following Chrome permissions:
9293

93-
- **storage**: To save your settings, token, and activity data locally
94+
- **storage**: To save your settings, GitHub sign-in state, and activity data locally
9495
- **alarms**: To periodically check for new repository activity
9596
- **notifications**: To show you browser notifications for new activity
9697
- **Host permission for api.github.com**: To fetch repository activity from GitHub's API
@@ -104,14 +105,14 @@ You have complete control over your data:
104105
- **View Your Data**: All settings are visible in the extension's options page
105106
- **Delete Your Data**: Uninstall the extension to remove all data, or use the "Clear All Data" option in settings
106107
- **Export Your Data**: Use the backup/restore feature to export your settings
107-
- **Revoke Access**: Remove or regenerate your GitHub Personal Access Token at any time via GitHub's settings
108+
- **Revoke Access**: Disconnect locally in DevWatch, and revoke the OAuth app in GitHub's authorized applications settings at any time
108109

109110
## Security
110111

111112
Current builds include several concrete safeguards:
112113

113114
- All API requests use HTTPS
114-
- The token is encrypted before it is persisted locally
115+
- The GitHub auth session is encrypted before it is persisted locally
115116
- The codebase includes input sanitization and GitHub URL validation checks
116117
- Extension pages use a Content Security Policy
117118

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Monitor pull requests, issues, and releases across multiple GitHub repositories
99

1010
## Key Features
1111

12-
- **Guided Setup** - Built-in setup flow for token creation and repository selection
12+
- **Guided Setup** - Built-in GitHub sign-in flow and repository selection
1313
- **Browser Notifications** - Get notified about new PRs, issues, and releases
1414
- **Multi-Repo Monitoring** - Watch up to 50 repositories from one interface
1515
- **Configurable Updates** - Check every 5, 15, 30, or 60 minutes
@@ -30,7 +30,7 @@ Monitor pull requests, issues, and releases across multiple GitHub repositories
3030
3. Grant permissions when prompted
3131
4. Follow the guided setup wizard on first launch
3232

33-
**GitHub Token Permissions**: You'll need a [Personal Access Token](https://github.com/settings/tokens/new) with `repo` (for private repos) or `public_repo` (for public only).
33+
**GitHub Sign-In Permissions**: DevWatch uses GitHub OAuth device flow and requests `repo` plus `read:user` so it can monitor private repositories and show the connected account in the UI.
3434

3535
### Manual Installation (For Development)
3636

@@ -53,7 +53,7 @@ cd devwatch-github
5353
### First-Time Setup
5454

5555
The built-in setup flow walks you through:
56-
1. Create a GitHub token
56+
1. Connect your GitHub account
5757
2. Add repositories to watch
5858
3. Choose activity types (PRs, Issues, Releases)
5959

@@ -74,7 +74,7 @@ The built-in setup flow walks you through:
7474
Filter by type (All/PRs/Issues/Releases), search activities, refresh manually, or browse the archive. Click any item to open in GitHub.
7575

7676
### Settings Page
77-
Manage your GitHub token, watched repositories, activity filters, check interval, notifications, and theme. Export/import settings for backup.
77+
Manage your GitHub connection, watched repositories, activity filters, check interval, notifications, and theme. Export/import settings for backup.
7878

7979
<div align="center">
8080
<img src="screenshots/settings-page.png" alt="Settings page for configuring repositories" width="600">
@@ -101,7 +101,7 @@ That said, this project has not gone through a formal accessibility audit or doc
101101

102102
## Privacy & Security Notes
103103

104-
The extension talks directly to GitHub's API and does not use a separate analytics or sync backend. It stores settings and cached activity in Chrome extension storage, and the current build encrypts the GitHub token before persisting it locally while keeping a decrypted session copy available at runtime.
104+
The extension talks directly to GitHub's API and does not use a separate analytics or sync backend. It stores settings and cached activity in Chrome extension storage, and the current build encrypts the GitHub auth session before persisting it locally while keeping a decrypted session copy available at runtime.
105105

106106
- **Direct network access** - Requests go to `api.github.com`, plus `registry.npmjs.org` only when you use package-name lookup
107107
- **Scoped browser permissions** - The manifest asks for `storage`, `alarms`, and `notifications`

SECURITY.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ I'll respond within 48 hours and work with you to understand and address the iss
1515

1616
Things I want to know about:
1717
- XSS vulnerabilities or ways to inject malicious code
18-
- Token leakage or insecure storage
18+
- OAuth session leakage or insecure storage
1919
- Ways to access other users' data
2020
- Privilege escalation
2121
- Dependencies with known CVEs
@@ -32,8 +32,8 @@ These are better suited for regular issues:
3232

3333
The extension includes several concrete protections, but this project has not been through a formal external security audit.
3434

35-
### Token Storage
36-
- GitHub tokens are encrypted before they are written to local extension storage
35+
### GitHub Sign-In Storage
36+
- GitHub auth sessions are encrypted before they are written to local extension storage
3737
- A decrypted copy may be cached in session storage while the extension is running
3838
- Never transmitted to third-party servers
3939

@@ -49,7 +49,7 @@ The extension includes several concrete protections, but this project has not be
4949

5050
### API Security
5151
- All requests use HTTPS
52-
- Tokens are included in headers, never in URLs
52+
- OAuth access tokens are included in headers, never in URLs
5353
- Rate limiting is respected to prevent abuse
5454

5555
## Supported Versions

background.js

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { createHeaders, handleApiResponse, mapActivity, filterActivitiesByDate } from './shared/github-api.js';
2-
import { getSyncItems, getLocalItems, setLocalItem, getExcludedRepos, getToken, getFilteringSettings } from './shared/storage-helpers.js';
2+
import { getSyncItems, getLocalItems, setLocalItem, getExcludedRepos, getAccessToken, getFilteringSettings, getWatchedRepos } from './shared/storage-helpers.js';
33
import { extractRepoName } from './shared/repository-utils.js';
44
import { safelyOpenUrl } from './shared/security.js';
55

@@ -73,11 +73,10 @@ if (typeof chrome !== 'undefined' && chrome.notifications) {
7373

7474
async function checkGitHubActivity() {
7575
try {
76-
// Get token from secure local storage
77-
const githubToken = await getToken();
76+
const githubToken = await getAccessToken();
7877

79-
const { watchedRepos, lastCheck, filters, notifications, mutedRepos, snoozedRepos, unmutedRepos } = await getSyncItems([
80-
'watchedRepos',
78+
const watchedRepos = await getWatchedRepos();
79+
const { lastCheck, filters, notifications, mutedRepos, snoozedRepos, unmutedRepos } = await getSyncItems([
8180
'lastCheck',
8281
'filters',
8382
'notifications',
@@ -87,7 +86,7 @@ async function checkGitHubActivity() {
8786
]);
8887

8988
if (!githubToken) {
90-
console.warn('[DevWatch] No GitHub token found. Please add a token in settings.');
89+
console.warn('[DevWatch] No GitHub connection found. Please connect GitHub in settings.');
9190
return;
9291
}
9392

@@ -287,7 +286,7 @@ async function fetchRepoActivity(repo, token, since, filters) {
287286
// Store error for user display but don't crash
288287
let userMessage = 'Unable to fetch repository activity';
289288
if (error.message.includes('401')) {
290-
userMessage = 'Authentication failed. Please check your GitHub token.';
289+
userMessage = 'GitHub sign-in expired or was revoked. Reconnect GitHub in settings.';
291290
} else if (error.message.includes('403')) {
292291
userMessage = 'Access denied or rate limit exceeded.';
293292
} else if (error.message.includes('404')) {

manifest.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@
1010
"notifications"
1111
],
1212
"host_permissions": [
13-
"https://api.github.com/*"
13+
"https://api.github.com/*",
14+
"https://github.com/*"
1415
],
1516
"background": {
1617
"service_worker": "background.js",
@@ -33,6 +34,6 @@
3334
"128": "icons/icon128.png"
3435
},
3536
"content_security_policy": {
36-
"extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://api.github.com https://registry.npmjs.org; img-src 'self' https: data:; default-src 'self'; style-src 'self'"
37+
"extension_pages": "script-src 'self'; object-src 'self'; connect-src 'self' https://api.github.com https://github.com https://registry.npmjs.org; img-src 'self' https: data:; default-src 'self'; style-src 'self'"
3738
}
3839
}

options/controllers/export-import-controller.js

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
11
import { NotificationManager } from '../../shared/ui/notification-manager.js';
2+
import { getWatchedRepos, setWatchedRepos } from '../../shared/storage-helpers.js';
23

34
const notifications = NotificationManager.getInstance();
45

56
export async function exportSettings() {
67
try {
78
const syncData = await chrome.storage.sync.get(null);
9+
const watchedRepos = await getWatchedRepos();
810

911
const exportData = {
1012
version: '1.0.0',
1113
exportedAt: new Date().toISOString(),
1214
settings: {
13-
watchedRepos: syncData.watchedRepos || [],
15+
watchedRepos,
1416
mutedRepos: syncData.mutedRepos || [],
1517
pinnedRepos: syncData.pinnedRepos || [],
1618
filters: syncData.filters || { prs: true, issues: true, releases: true },
@@ -53,7 +55,7 @@ export async function handleImportFile(event, loadSettingsCallback) {
5355
}
5456

5557
const confirmed = confirm(
56-
'This will replace your current settings (except GitHub token). Continue?'
58+
'This will replace your current settings (except your GitHub connection). Continue?'
5759
);
5860

5961
if (!confirmed) {
@@ -63,8 +65,8 @@ export async function handleImportFile(event, loadSettingsCallback) {
6365
}
6466

6567
const settings = importData.settings;
68+
await setWatchedRepos(settings.watchedRepos || []);
6669
await chrome.storage.sync.set({
67-
watchedRepos: settings.watchedRepos || [],
6870
mutedRepos: settings.mutedRepos || [],
6971
pinnedRepos: settings.pinnedRepos || [],
7072
filters: settings.filters || { prs: true, issues: true, releases: true },

0 commit comments

Comments
 (0)