Skip to content

Commit d640f14

Browse files
authored
OAuth (#22)
* add metadata * test pages action * initial oauth flow * update scope * login in settings * remove session from plugin class * remove oauth modal * add session store / refactor handle missing block text * render before done fetching feed * initila work for redirect to obsidian uri scheme * switch back to browser client * cleanup storing session/agent * cleanup * better html page * better settings layout
1 parent 1418f94 commit d640f14

27 files changed

Lines changed: 6217 additions & 145 deletions
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
name: Deploy static content to Pages
2+
3+
on:
4+
push:
5+
tags:
6+
- "*"
7+
8+
# Allows you to run this workflow manually from the Actions tab
9+
workflow_dispatch:
10+
11+
permissions:
12+
contents: read
13+
pages: write
14+
id-token: write
15+
16+
concurrency:
17+
group: "pages"
18+
cancel-in-progress: false
19+
20+
jobs:
21+
deploy:
22+
environment:
23+
name: github-pages
24+
url: ${{ steps.deployment.outputs.page_url }}
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v4
29+
- name: Setup Pages
30+
uses: actions/configure-pages@v5
31+
- name: Prepare static files
32+
run: |
33+
mkdir -p _site
34+
cp client-metadata.json _site/
35+
cp oauth-callback.html _site/
36+
- name: Upload artifact
37+
uses: actions/upload-pages-artifact@v3
38+
with:
39+
path: '_site'
40+
- name: Deploy to GitHub Pages
41+
id: deployment
42+
uses: actions/deploy-pages@v4

.github/workflows/lint.yml

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,37 @@
11
name: Bun build
22

33
on:
4-
push:
5-
branches: ["**"]
6-
pull_request:
7-
branches: ["**"]
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
branches: ["**"]
88

9-
jobs:
10-
build:
11-
runs-on: ubuntu-latest
9+
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
10+
permissions:
11+
contents: read
12+
pages: write
13+
id-token: write
14+
15+
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
16+
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
17+
concurrency:
18+
group: "pages"
19+
cancel-in-progress: false
1220

13-
strategy:
14-
matrix:
15-
bun-version: [latest]
21+
jobs:
22+
build:
23+
runs-on: ubuntu-latest
1624

17-
steps:
18-
- uses: actions/checkout@v4
19-
- name: Setup Bun ${{ matrix.bun-version }}
20-
uses: oven-sh/setup-bun@v2
21-
with:
22-
bun-version: ${{ matrix.bun-version }}
23-
- run: bun install --frozen-lockfile
24-
- run: bun run build --if-present
25-
- run: bun run lint
25+
strategy:
26+
matrix:
27+
bun-version: [latest]
2628

29+
steps:
30+
- uses: actions/checkout@v4
31+
- name: Setup Bun ${{ matrix.bun-version }}
32+
uses: oven-sh/setup-bun@v2
33+
with:
34+
bun-version: ${{ matrix.bun-version }}
35+
- run: bun install --frozen-lockfile
36+
- run: bun run build --if-present
37+
- run: bun run lint

bun.lock

Lines changed: 11 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

client-metadata.json

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"client_id": "https://treethought.github.io/obsidian-atmosphere/client-metadata.json",
3+
"client_name": "obsidian-atmosphere",
4+
"client_uri": "https://treethought.github.io/obsidian-atmosphere",
5+
"redirect_uris": [
6+
"https://treethought.github.io/obsidian-atmosphere/oauth-callback.html"
7+
],
8+
"scope": "atproto include:at.margin.authFull repo:site.standard.document repo:network.cosmik.card repo:network.cosmik.collection repo:network.cosmik.collectionLink",
9+
"grant_types": ["authorization_code", "refresh_token"],
10+
"response_types": ["code"],
11+
"token_endpoint_auth_method": "none",
12+
"application_type": "native",
13+
"dpop_bound_access_tokens": true
14+
}

esbuild.config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import process from "process";
33
import { builtinModules } from 'node:module';
44

55
const banner =
6-
`/*
6+
`/*
77
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
88
if you want to view the source, please visit the github repository of this plugin
99
*/

oauth-callback.html

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>Atmosphere OAuth - Redirecting...</title>
7+
<style>
8+
* {
9+
margin: 0;
10+
padding: 0;
11+
box-sizing: border-box;
12+
}
13+
body {
14+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
15+
display: flex;
16+
align-items: center;
17+
justify-content: center;
18+
min-height: 100vh;
19+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
20+
padding: 1rem;
21+
}
22+
.container {
23+
text-align: center;
24+
padding: 3rem 2rem;
25+
background: white;
26+
border-radius: 16px;
27+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
28+
max-width: 500px;
29+
width: 100%;
30+
}
31+
h1 {
32+
color: #667eea;
33+
margin: 0 0 1rem 0;
34+
font-size: 1.75rem;
35+
font-weight: 600;
36+
}
37+
.spinner {
38+
margin: 2rem auto;
39+
width: 50px;
40+
height: 50px;
41+
border: 4px solid #f3f3f3;
42+
border-top: 4px solid #667eea;
43+
border-radius: 50%;
44+
animation: spin 1s linear infinite;
45+
}
46+
@keyframes spin {
47+
0% { transform: rotate(0deg); }
48+
100% { transform: rotate(360deg); }
49+
}
50+
p {
51+
color: #6b7280;
52+
margin: 1rem 0;
53+
line-height: 1.6;
54+
}
55+
.manual-link {
56+
margin-top: 2rem;
57+
padding: 1rem;
58+
background: #f9fafb;
59+
border-radius: 8px;
60+
border: 1px solid #e5e7eb;
61+
display: none;
62+
}
63+
.manual-link.show {
64+
display: block;
65+
}
66+
.link-text {
67+
word-break: break-all;
68+
font-family: monospace;
69+
font-size: 0.85rem;
70+
color: #374151;
71+
padding: 0.5rem;
72+
background: white;
73+
border-radius: 4px;
74+
margin-top: 0.5rem;
75+
}
76+
button {
77+
margin-top: 1rem;
78+
padding: 0.75rem 1.5rem;
79+
background: #667eea;
80+
color: white;
81+
border: none;
82+
border-radius: 8px;
83+
font-size: 1rem;
84+
font-weight: 500;
85+
cursor: pointer;
86+
transition: background 0.2s;
87+
}
88+
button:hover {
89+
background: #5568d3;
90+
}
91+
</style>
92+
</head>
93+
<body>
94+
<div class="container">
95+
<h1>✅ Authentication Successful!</h1>
96+
<div class="spinner"></div>
97+
<p id="status">Redirecting to Obsidian...</p>
98+
<div class="manual-link" id="manual-link">
99+
<p>If Obsidian doesn't open automatically:</p>
100+
<p style="font-size: 0.9rem; margin-bottom: 0.5rem;">1. Copy the link below</p>
101+
<div class="link-text" id="link-text"></div>
102+
<button onclick="copyLink()">Copy Link</button>
103+
<p style="font-size: 0.9rem; margin-top: 1rem;">2. Open Obsidian and paste it in your browser</p>
104+
</div>
105+
</div>
106+
107+
<script>
108+
(function() {
109+
try {
110+
// extract OAuth parameters from URL hash (not search string)
111+
const params = new URLSearchParams(window.location.hash.slice(1));
112+
113+
const obsidianUri = `obsidian://atmosphere-oauth?${params.toString()}`;
114+
115+
// store the URI for manual copy
116+
document.getElementById('link-text').textContent = obsidianUri;
117+
118+
window.location.href = obsidianUri;
119+
120+
setTimeout(function() {
121+
const spinner = document.querySelector('.spinner');
122+
if (spinner) spinner.style.display = 'none';
123+
124+
// Show success message
125+
document.querySelector('h1').textContent = '✅ Redirected!';
126+
document.getElementById('status').textContent = 'Return to Obsidian to complete login.';
127+
128+
setTimeout(function() {
129+
try {
130+
window.close();
131+
} catch (e) {
132+
document.getElementById('status').textContent = 'You can close this window now.';
133+
}
134+
}, 500);
135+
}, 500);
136+
137+
// show manual instructions after a longer delay if still open
138+
setTimeout(function() {
139+
document.getElementById('manual-link').classList.add('show');
140+
}, 3000);
141+
142+
} catch (error) {
143+
console.error('Redirect error:', error);
144+
document.querySelector('.spinner').style.display = 'none';
145+
document.getElementById('status').textContent = 'An error occurred during redirect';
146+
document.getElementById('manual-link').classList.add('show');
147+
}
148+
})();
149+
150+
function copyLink() {
151+
const linkText = document.getElementById('link-text').textContent;
152+
navigator.clipboard.writeText(linkText).then(function() {
153+
const btn = event.target;
154+
btn.textContent = '✓ Copied!';
155+
setTimeout(function() {
156+
btn.textContent = 'Copy Link';
157+
}, 2000);
158+
}).catch(function(err) {
159+
console.error('Failed to copy:', err);
160+
alert('Failed to copy. Please select and copy the link manually.');
161+
});
162+
}
163+
</script>
164+
</body>
165+
</html>

0 commit comments

Comments
 (0)