Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
4de6e67
Validate Token Part 1
yakov116 Nov 25, 2020
35cb0af
Remove unrelated change
yakov116 Nov 25, 2020
cf88c86
Push changes cleanup start later
yakov116 Nov 25, 2020
0c5205b
no store
yakov116 Nov 26, 2020
89444fd
Start transfer
yakov116 Nov 27, 2020
1b476fd
next step
yakov116 Nov 27, 2020
7140084
Clean up
yakov116 Nov 27, 2020
632e16b
Cleanup some more
yakov116 Nov 27, 2020
7861826
Comments
yakov116 Nov 27, 2020
da2e3ea
Remove Loading
yakov116 Nov 27, 2020
d1be27d
Small changes
yakov116 Nov 27, 2020
97a1a90
Better idea
yakov116 Nov 27, 2020
8148b18
Lint
yakov116 Nov 28, 2020
f078d58
Merge remote-tracking branch 'upstream/master' into validate-token
yakov116 Dec 4, 2020
6aa5f09
Start feedback
yakov116 Dec 6, 2020
81216af
Merge branch 'master' into validate-token
yakov116 Dec 6, 2020
394e8de
next
yakov116 Dec 16, 2020
df59e6b
Merge remote-tracking branch 'upstream/master' into validate-token
yakov116 Jan 1, 2021
a5691bf
Switch to item list
yakov116 Jan 1, 2021
0fc86f6
Restore list style on no token
yakov116 Jan 1, 2021
ffb803c
Merge remote-tracking branch 'upstream/master' into validate-token
yakov116 Jan 3, 2021
23a7e1a
Fix Select
yakov116 Jan 3, 2021
434b7b8
Better Replacement
yakov116 Jan 3, 2021
cd95b74
Feedback
yakov116 Jan 3, 2021
6e97080
Rename var
yakov116 Jan 3, 2021
c0125c5
Lint
yakov116 Jan 3, 2021
710f69a
Change Validated wording
yakov116 Jan 3, 2021
b958f28
Use ::before
yakov116 Jan 4, 2021
1b47746
Update source/options.tsx
yakov116 Jan 4, 2021
ad8ff47
Merge remote-tracking branch 'upstream/validate-token' into validate-…
yakov116 Jan 4, 2021
e520718
Use Octicons + merge UI updater into single function
fregante Jan 4, 2021
94fc585
Avoid ()!
fregante Jan 4, 2021
63686c1
Lint
fregante Jan 4, 2021
b929ff1
Restore test/web-ext-profile/.gitkeep
fregante Jan 5, 2021
f33415f
Sort code
fregante Jan 5, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions distribution/options.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@
<strong>Personal token</strong> (optional, <a id="personal-token-link" href="https://github.com/settings/tokens/new?description=Refined%20GitHub&scopes=repo" target="_blank">generate one</a>)<br>
<!-- placeholder is set to enable use of :placeholder-shown CSS selector -->
<input type="text" name="personalToken" spellcheck="false" autocomplete="off" pattern="[\da-f]{40}" placeholder=" ">
<span id="validation"></span>
</label>
</p>
<p>
The token enables <a href="https://github.com/sindresorhus/refined-github/search?q=github-helpers+api" target="_blank">some features</a> to <strong>read</strong> data from public repositories
</p>
<ul>
<li>The token enables <a href="https://github.com/sindresorhus/refined-github/search?q=github-helpers+api" target="_blank">some features</a> to read data from public repositories
<li>The <code>public_repo</code> scope lets them edit your public repositories
<li>The <code>repo</code> scope lets them edit private repositories as well
<li>The <code>delete_repo</code> scope is only used by the <code>quick-fork-deletion</code> feature
<li data-validation data-scope="public_repo">The <code>public_repo</code> scope lets them <strong>edit</strong> your public repositories
<li data-validation data-scope="repo">The <code>repo</code> scope lets them <strong>edit private</strong> repositories as well
<li data-validation data-scope="delete_repo">The <code>delete_repo</code> scope is only used by the <code>quick-repo-deletion</code> feature
</ul>

<hr>
Expand Down
25 changes: 24 additions & 1 deletion source/options.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,29 @@ p {
}

ul {
padding-left: 1.5em;
padding-left: 0;
list-style: none;
}

li[data-validation] {
margin-bottom: 0.3em;
}

[data-validation]::before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" fill="gray" d="M8 5.5a2.5 2.5 0 100 5 2.5 2.5 0 000-5zM4 8a4 4 0 118 0 4 4 0 01-8 0z"></path></svg>');
width: 16px;
height: 16px;
vertical-align: -4px;
margin-right: 0.3em;
display: inline-block;
}

[data-validation='valid']::before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" fill="%2328a745" d="M8 16A8 8 0 108 0a8 8 0 000 16zm3.78-9.72a.75.75 0 00-1.06-1.06L6.75 9.19 5.28 7.72a.75.75 0 00-1.06 1.06l2 2a.75.75 0 001.06 0l4.5-4.5z"></path></svg>');
}

[data-validation='invalid']::before {
content: url('data:image/svg+xml; utf8, <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path fill-rule="evenodd" fill="%23cb2431" d="M1.5 8a6.5 6.5 0 0110.535-5.096l-9.131 9.131A6.472 6.472 0 011.5 8zm2.465 5.096a6.5 6.5 0 009.131-9.131l-9.131 9.131zM8 0a8 8 0 100 16A8 8 0 008 0z"></path></svg>');
}

:root [name='customCSS'],
Expand All @@ -25,6 +47,7 @@ ul {

[name='personalToken'] {
width: 20em !important; /* https://github.com/sindresorhus/refined-github/issues/1374#issuecomment-397906701 */
display: inline-block !important;
}

[name='personalToken']:invalid {
Expand Down
82 changes: 80 additions & 2 deletions source/options.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,77 @@ import * as indentTextarea from 'indent-textarea';

import {perDomainOptions} from './options-storage';

interface Status {
error?: true;
text?: string;
scopes?: string[];
}

function reportStatus({error, text, scopes}: Status): void {
const tokenStatus = select('#validation')!;
tokenStatus.textContent = text ?? '';
if (error) {
tokenStatus.dataset.validation = 'invalid';
} else {
delete tokenStatus.dataset.validation;
}

for (const scope of select.all('[data-scope]')) {
if (scopes) {
scope.dataset.validation = scopes.includes(scope.dataset.scope!) ? 'valid' : 'invalid';
} else {
scope.dataset.validation = '';
}
}
}

async function getTokenScopes(personalToken: string): Promise<string[]> {
const tokenLink = select('a#personal-token-link')!;
const url = tokenLink.host === 'github.com' ?
'https://api.github.com/' :
`${tokenLink.origin}/api/v3/`;

const response = await fetch(url, {
cache: 'no-store',
headers: {
'User-Agent': 'Refined GitHub',
Accept: 'application/vnd.github.v3+json',
Authorization: `token ${personalToken}`
}
});

if (!response.ok) {
const details = await response.json();
throw new Error(details.message);
}

const scopes = response.headers.get('X-OAuth-Scopes')!.split(', ');
if (scopes.includes('repo')) {
scopes.push('public_repo');
}

return scopes;
}

async function validateToken(): Promise<void> {
reportStatus({});
const tokenField = select('input[name="personalToken"]')!;
if (!tokenField.validity.valid || tokenField.value.length === 0) {
return;
}

reportStatus({text: 'Validating…'});

try {
reportStatus({
scopes: await getTokenScopes(tokenField.value)
});
} catch (error: unknown) {
reportStatus({error: true, text: (error as Error).message});
throw error;
}
}

function moveDisabledFeaturesToTop(): void {
const container = select('.js-features')!;
for (const unchecked of select.all('.feature [type=checkbox]:not(:checked)', container).reverse()) {
Expand Down Expand Up @@ -90,6 +161,7 @@ async function generateDom(): Promise<void> {
// Decorate list
moveDisabledFeaturesToTop();
void highlightNewFeatures();
void validateToken();

// Move debugging tools higher when side-loaded
if (process.env.NODE_ENV === 'development') {
Expand All @@ -99,8 +171,11 @@ async function generateDom(): Promise<void> {

function addEventListeners(): void {
// Update domain-dependent page content when the domain is changed
select('.js-options-sync-selector')?.addEventListener('change', ({currentTarget: dropdown}) => {
select('a#personal-token-link')!.host = (dropdown as HTMLSelectElement).value;
select('.OptionsSyncPerDomain-picker select')?.addEventListener('change', ({currentTarget: dropdown}) => {
const host = (dropdown as HTMLSelectElement).value;
select('a#personal-token-link')!.host = host === 'default' ? 'github.com' : host;
// Delay validating to let options load first
setTimeout(validateToken, 100);
});

// Refresh page when permissions are changed (because the dropdown selector needs to be regenerated)
Expand All @@ -121,6 +196,9 @@ function addEventListeners(): void {
// Add cache clearer
select('#clear-cache')!.addEventListener('click', clearCacheHandler);

// Add token validation
select('[name="personalToken"]')!.addEventListener('input', validateToken);

// Ensure all links open in a new tab #3181
delegate(document, '[href^="http"]', 'click', (event: delegate.Event<MouseEvent, HTMLAnchorElement>) => {
event.preventDefault();
Expand Down