Skip to content

Commit

Permalink
feat: implement basic login mode
Browse files Browse the repository at this point in the history
fixes #1542
  • Loading branch information
nolanlawson committed Sep 26, 2019
1 parent 8fbf38e commit f490d1d
Show file tree
Hide file tree
Showing 6 changed files with 202 additions and 87 deletions.
38 changes: 31 additions & 7 deletions src/routes/_actions/addInstance.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,17 @@ import { updateCustomEmojiForInstance } from './emoji'
import { database } from '../_database/database'
import { DOMAIN_BLOCKS } from '../_static/blocks'

const REDIRECT_URI = process.browser && `${location.origin}/settings/instances/add`

function createKnownError (message) {
const err = new Error(message)
err.knownError = true
return err
}

function getRedirectUri () {
const { copyPasteMode } = store.get()
return copyPasteMode ? 'urn:ietf:wg:oauth:2.0:oob' : `${location.origin}/settings/instances/add`
}

async function redirectToOauth () {
let { instanceNameInSearch, loggedInInstances } = store.get()
instanceNameInSearch = instanceNameInSearch.replace(/^https?:\/\//, '').replace(/\/+$/, '').toLowerCase()
Expand All @@ -26,7 +29,8 @@ async function redirectToOauth () {
if (DOMAIN_BLOCKS.some(domain => new RegExp(`(?:\\.|^)${domain}$`, 'i').test(instanceHostname))) {
throw createKnownError('This service is blocked')
}
const registrationPromise = registerApplication(instanceNameInSearch, REDIRECT_URI)
const redirectUri = getRedirectUri()
const registrationPromise = registerApplication(instanceNameInSearch, redirectUri)
const instanceInfo = await getInstanceInfo(instanceNameInSearch)
await database.setInstanceInfo(instanceNameInSearch, instanceInfo) // cache for later
const instanceData = await registrationPromise
Expand All @@ -38,11 +42,16 @@ async function redirectToOauth () {
const oauthUrl = generateAuthLink(
instanceNameInSearch,
instanceData.client_id,
REDIRECT_URI
redirectUri
)
// setTimeout to allow the browser to *actually* save the localStorage data (fixes Safari bug apparently)
const { copyPasteMode } = store.get()
setTimeout(() => {
document.location.href = oauthUrl
if (copyPasteMode) {
window.open(oauthUrl, '_blank', 'noopener')
} else {
document.location.href = oauthUrl
}
}, 200)
}

Expand Down Expand Up @@ -72,12 +81,13 @@ export async function logInToInstance () {

async function registerNewInstance (code) {
const { currentRegisteredInstanceName, currentRegisteredInstance } = store.get()
const redirectUri = getRedirectUri()
const instanceData = await getAccessTokenFromAuthCode(
currentRegisteredInstanceName,
currentRegisteredInstance.client_id,
currentRegisteredInstance.client_secret,
code,
REDIRECT_URI
redirectUri
)
const { loggedInInstances, loggedInInstancesInOrder, instanceThemes } = store.get()
instanceThemes[currentRegisteredInstanceName] = DEFAULT_THEME
Expand All @@ -92,7 +102,8 @@ async function registerNewInstance (code) {
loggedInInstances: loggedInInstances,
currentInstance: currentRegisteredInstanceName,
loggedInInstancesInOrder: loggedInInstancesInOrder,
instanceThemes: instanceThemes
instanceThemes: instanceThemes,
copyPasteMode: false
})
store.save()
const { enableGrayscale } = store.get()
Expand All @@ -113,3 +124,16 @@ export async function handleOauthCode (code) {
store.set({ logInToInstanceLoading: false })
}
}

export async function handleCopyPasteOauthCode (code) {
const { currentRegisteredInstanceName, currentRegisteredInstance } = store.get()
if (!currentRegisteredInstanceName || !currentRegisteredInstance) {
store.set({
logInToInstanceError: 'You must log in to an instance first.',
logInToInstanceErrorForText: '',
instanceNameInSearch: ''
})
} else {
await handleOauthCode(code)
}
}
40 changes: 40 additions & 0 deletions src/routes/_components/InfoAside.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<aside class="info-aside {className}">
<SvgIcon href="#fa-info-circle" className="aside-icon" />
<span>
<slot></slot>
</span>
</aside>
<style>
.info-aside {
font-size: 1.2em;
color: var(--deemphasized-text-color);
display: flex;
align-items: center;
}
:global(.info-aside a) {
text-decoration: underline;
color: var(--deemphasized-text-color);
}
:global(.info-aside span) {
flex: 1;
}
:global(.aside-icon) {
fill: var(--deemphasized-text-color);
width: 18px;
height: 18px;
margin: 0 10px 0 5px;
min-width: 18px;
}
</style>
<script>
import SvgIcon from './SvgIcon.html'

export default {
data: () => ({
className: ''
}),
components: {
SvgIcon
}
}
</script>
157 changes: 108 additions & 49 deletions src/routes/_pages/settings/instances/add.html
Original file line number Diff line number Diff line change
@@ -1,51 +1,91 @@
<SettingsLayout page='settings/instances/add' label="Add instance">
<h1 id="add-an-instance-h1">Add instance</h1>

<form class="add-new-instance" on:submit='onSubmit(event)' aria-labelledby="add-an-instance-h1">
<div class="add-new-instance">
<form on:submit='onSubmitInstance(event)' aria-labelledby="add-an-instance-h1">

{#if !hasIndexedDB || !hasLocalStorage}
<div class="form-error form-error-user-error" role="alert">
It seems Pinafore cannot store data locally. Is your browser in private mode
or blocking cookies? Pinafore stores all data locally, and requires LocalStorage and
IndexedDB to work correctly.
</div>
{/if}
{#if !hasIndexedDB || !hasLocalStorage}
<div class="form-error form-error-user-error" role="alert">
It seems Pinafore cannot store data locally. Is your browser in private mode
or blocking cookies? Pinafore stores all data locally, and requires LocalStorage and
IndexedDB to work correctly.
</div>
{/if}

{#if $logInToInstanceError && $logInToInstanceErrorForText === $instanceNameInSearch}
<div class="form-error form-error-user-error" role="alert">
Error: {$logInToInstanceError}
</div>
{/if}
{#if $logInToInstanceError && $logInToInstanceErrorForText === $instanceNameInSearch}
<div class="form-error form-error-user-error" role="alert">
Error: {$logInToInstanceError}
</div>
{/if}

<noscript>
<div class="form-error" role="alert">
You must enable JavaScript to log in.
</div>
</noscript>
<noscript>
<div class="form-error" role="alert">
You must enable JavaScript to log in.
</div>
</noscript>

<label class="add-new-instance-label" for="instanceInput">Instance:</label>
<input class="add-new-instance-input" type="text" inputmode="url" id="instanceInput"
bind:value='$instanceNameInSearch' placeholder="Enter instance name" required
>
<button class="primary add-new-instance-button" type="submit" id="submitButton"
disabled={!$instanceNameInSearch || $logInToInstanceLoading}>
Log in
</button>
</form>
<label for="instanceInput">Instance:</label>
<input type="text" inputmode="url" id="instanceInput"
bind:value='$instanceNameInSearch' placeholder="Enter instance name" required
>
<button class="primary" type="submit" id="submitButton"
disabled={!$instanceNameInSearch || $logInToInstanceLoading}>
Log in
</button>
</form>

{#if $copyPasteMode }
<form aria-label="Enter code" on:submit="onSubmitOauth(event)">
<label for="oauthCodeInput">Code:</label>
<input type="text" id="oauthCodeInput"
bind:value='oauthCode' placeholder="Enter code" required
>
<button class="primary" type="submit" id="submitOauthButton"
disabled={!oauthCode}>
Submit
</button>
</form>
{/if}
</div>

{#if !$isUserLoggedIn}
<p>
Don't have an
<Tooltip
text="instance"
tooltipText="An instance is your Mastodon home server, such as mastodon.social or cybre.space."
/>
?
<ExternalLink href="https://joinmastodon.org">Join Mastodon!</ExternalLink>
</p>
{/if}
<p>
Don't have an
<Tooltip
text="instance"
tooltipText="An instance is your Mastodon home server, such as mastodon.social or cybre.space."
/>
?
<ExternalLink href="https://joinmastodon.org">Join Mastodon!</ExternalLink>
{#if $copyPasteMode}
Switch back to
{:else}
Trouble logging in? Switch to
{/if}
<button on:click="onCopyPasteModeButtonClick()"
class="copy-paste-mode-button"
aria-pressed={$copyPasteMode}>
{$copyPasteMode ? 'regular' : 'basic'} login mode
</button>.
</p>
{#if $copyPasteMode}
<InfoAside className="add-new-instance-aside">
In basic login mode, click "log in" to open a new window. Then copy the code and paste it above.
</InfoAside>
{/if}
</SettingsLayout>
<style>
.add-new-instance {
background: var(--form-bg);
padding: 5px 10px 15px;
margin: 20px auto;
border: 1px solid var(--form-border);
border-radius: 4px;
}

.form-error {
border: 2px solid red;
border-radius: 2px;
Expand All @@ -54,27 +94,33 @@ <h1 id="add-an-instance-h1">Add instance</h1>
margin: 5px;
background-color: var(--main-bg);
}
.add-new-instance-input {
input {
min-width: 70%;
max-width: 100%;
background-color: var(--input-bg);
}

.add-new-instance {
background: var(--form-bg);
padding: 5px 10px 15px;
margin: 20px auto;
border: 1px solid var(--form-border);
border-radius: 4px;
}

.add-new-instance-label, .add-new-instance-input, .add-new-instance-button {
label, input, button, :global(.add-new-instance-aside) {
display: block;
margin: 20px 5px;
}

button.copy-paste-mode-button {
margin: 0;
padding: 0;
display: inline-block;
background: none;
border: none;
font-size: 1em;
color: var(--anchor-text);
}

button.copy-paste-mode-button:hover {
text-decoration: underline;
}

@media (max-width: 767px) {
.add-new-instance-input {
input {
min-width: 95%;
}
}
Expand All @@ -83,10 +129,11 @@ <h1 id="add-an-instance-h1">Add instance</h1>
<script>
import SettingsLayout from '../../../_components/settings/SettingsLayout.html'
import { store } from '../../../_store/store'
import { logInToInstance, handleOauthCode } from '../../../_actions/addInstance'
import { logInToInstance, handleOauthCode, handleCopyPasteOauthCode } from '../../../_actions/addInstance'
import ExternalLink from '../../../_components/ExternalLink.html'
import { testHasIndexedDB, testHasLocalStorage } from '../../../_utils/testStorage'
import Tooltip from '../../../_components/Tooltip.html'
import InfoAside from '../../../_components/InfoAside.html'

export default {
async oncreate () {
Expand All @@ -102,18 +149,30 @@ <h1 id="add-an-instance-h1">Add instance</h1>
components: {
SettingsLayout,
ExternalLink,
Tooltip
Tooltip,
InfoAside
},
store: () => store,
data: () => ({
hasIndexedDB: true,
hasLocalStorage: true
hasLocalStorage: true,
oauthCode: ''
}),
methods: {
onSubmit (event) {
onSubmitInstance (event) {
event.preventDefault()
event.stopPropagation()
logInToInstance()
},
onSubmitOauth (event) {
event.preventDefault()
event.stopPropagation()
handleCopyPasteOauthCode(this.get().oauthCode)
},
onCopyPasteModeButtonClick () {
const { copyPasteMode } = this.store.get()
console.log('copyPasteMode', copyPasteMode)
this.store.set({ copyPasteMode: !copyPasteMode })
}
}
}
Expand Down
Loading

0 comments on commit f490d1d

Please sign in to comment.