WIP on multi-instance login (#22)
Something isn't lining up between the client and server (need to debug the URLs they're both using)
This commit is contained in:
@@ -2,6 +2,7 @@ import {
|
||||
Citizen,
|
||||
Continent,
|
||||
Count,
|
||||
Instance,
|
||||
Listing,
|
||||
ListingExpireForm,
|
||||
ListingForm,
|
||||
@@ -100,11 +101,12 @@ export default {
|
||||
/**
|
||||
* Log a citizen on
|
||||
*
|
||||
* @param code The authorization code from No Agenda Social
|
||||
* @param abbr The abbreviation of the Mastodon instance that issued the code
|
||||
* @param code The authorization code from Mastodon
|
||||
* @returns The user result, or an error
|
||||
*/
|
||||
logOn: async (code : string) : Promise<LogOnSuccess | string> => {
|
||||
const resp = await fetch(apiUrl(`citizen/log-on/${code}`), { method: "GET", mode: "cors" })
|
||||
logOn: async (abbr : string, code : string) : Promise<LogOnSuccess | string> => {
|
||||
const resp = await fetch(apiUrl(`citizen/log-on/${abbr}/${code}`), { method: "GET", mode: "cors" })
|
||||
if (resp.status === 200) return await resp.json() as LogOnSuccess
|
||||
return `Error logging on - ${await resp.text()}`
|
||||
},
|
||||
@@ -141,6 +143,27 @@ export default {
|
||||
apiResult<Continent[]>(await fetch(apiUrl("continents"), { method: "GET" }), "retrieving continents")
|
||||
},
|
||||
|
||||
/** API functions for instances */
|
||||
instances: {
|
||||
|
||||
/**
|
||||
* Get all Mastodon instances we support
|
||||
*
|
||||
* @returns All instances, or an error
|
||||
*/
|
||||
all: async () : Promise<Instance[] | string | undefined> =>
|
||||
apiResult<Instance[]>(await fetch(apiUrl("instances"), { method: "GET" }), "retrieving Mastodon instances"),
|
||||
|
||||
/**
|
||||
* Retrieve a Mastodon instance by its abbreviation
|
||||
*
|
||||
* @param abbr The abbreviation of the Mastodon instance to retrieve
|
||||
* @returns The Mastodon instance (if found), undefined (if not found), or an error string
|
||||
*/
|
||||
byAbbr: async (abbr : string) : Promise<Instance | string | undefined> =>
|
||||
apiResult<Instance>(await fetch(apiUrl(`instance/${abbr}`), { method: "GET" }), "retrieving Mastodon instance")
|
||||
},
|
||||
|
||||
/** API functions for job listings */
|
||||
listings: {
|
||||
|
||||
|
||||
@@ -31,6 +31,18 @@ export interface Count {
|
||||
count : number
|
||||
}
|
||||
|
||||
/** The Mastodon instance data provided via the Jobs, Jobs, Jobs API */
|
||||
export interface Instance {
|
||||
/** The name of the instance */
|
||||
name : string
|
||||
/** The URL for this instance */
|
||||
url : string
|
||||
/** The abbreviation used in the URL to distinguish this instance's return codes */
|
||||
abbr : string
|
||||
/** The client ID (assigned by the Mastodon server) */
|
||||
clientId : string
|
||||
}
|
||||
|
||||
/** A job listing */
|
||||
export interface Listing {
|
||||
/** The ID of the job listing */
|
||||
|
||||
@@ -53,7 +53,7 @@ const routes: Array<RouteRecordRaw> = [
|
||||
component: LogOn
|
||||
},
|
||||
{
|
||||
path: "/citizen/authorized",
|
||||
path: "/citizen/:abbr/authorized",
|
||||
name: "CitizenAuthorized",
|
||||
component: () => import(/* webpackChunkName: "dashboard" */ "../views/citizen/Authorized.vue")
|
||||
},
|
||||
|
||||
@@ -43,8 +43,8 @@ export default createStore({
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
async logOn ({ commit }, code: string) {
|
||||
const logOnResult = await api.citizen.logOn(code)
|
||||
async logOn ({ commit }, { abbr, code }) {
|
||||
const logOnResult = await api.citizen.logOn(abbr, code)
|
||||
if (typeof logOnResult === "string") {
|
||||
commit("setLogOnState", logOnResult)
|
||||
} else {
|
||||
|
||||
@@ -7,30 +7,44 @@ article
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted } from "vue"
|
||||
import { useRouter } from "vue-router"
|
||||
import { useRoute, useRouter } from "vue-router"
|
||||
import api from "@/api"
|
||||
import { useStore } from "@/store"
|
||||
import { AFTER_LOG_ON_URL } from "@/router"
|
||||
|
||||
const router = useRouter()
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
/** The abbreviation of the instance from which we received the code */
|
||||
const abbr = route.params.abbr as string
|
||||
|
||||
/** Set the message for this component */
|
||||
const setMessage = (msg : string) => store.commit("setLogOnState", msg)
|
||||
|
||||
/** Pass the code to the API and exchange it for a user and a JWT */
|
||||
const logOn = async () => {
|
||||
const code = router.currentRoute.value.query.code
|
||||
if (code) {
|
||||
await store.dispatch("logOn", code)
|
||||
if (store.state.user !== undefined) {
|
||||
const afterLogOnUrl = window.localStorage.getItem(AFTER_LOG_ON_URL)
|
||||
if (afterLogOnUrl) {
|
||||
window.localStorage.removeItem(AFTER_LOG_ON_URL)
|
||||
router.push(afterLogOnUrl)
|
||||
} else {
|
||||
router.push("/citizen/dashboard")
|
||||
}
|
||||
}
|
||||
const instance = await api.instances.byAbbr(abbr)
|
||||
if (typeof instance === "string") {
|
||||
setMessage(instance)
|
||||
} else if (typeof instance === "undefined") {
|
||||
setMessage(`Mastodon instance ${abbr} not found`)
|
||||
} else {
|
||||
store.commit("setLogOnState",
|
||||
"Did not receive a token from No Agenda Social (perhaps you clicked “Cancel”?)")
|
||||
const code = route.query.code
|
||||
if (code) {
|
||||
await store.dispatch("logOn", { abbr, code })
|
||||
if (store.state.user !== undefined) {
|
||||
const afterLogOnUrl = window.localStorage.getItem(AFTER_LOG_ON_URL)
|
||||
if (afterLogOnUrl) {
|
||||
window.localStorage.removeItem(AFTER_LOG_ON_URL)
|
||||
router.push(afterLogOnUrl)
|
||||
} else {
|
||||
router.push("/citizen/dashboard")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
setMessage(`Did not receive a token from ${instance.name} (perhaps you clicked “Cancel”?)`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,24 +1,58 @@
|
||||
<template lang="pug">
|
||||
article
|
||||
p
|
||||
p.fst-italic Sending you over to No Agenda Social to log on; see you back in just a second…
|
||||
load-data(:load="retrieveInstances")
|
||||
p.fst-italic(v-if="selected") Sending you over to {{selected.name}} to log on; see you back in just a second…
|
||||
template(v-else)
|
||||
p.text-center Please select your No Agenda-affiliated Mastodon instance
|
||||
p.text-center(v-for="it in instances" :key="it.abbr")
|
||||
button.btn.btn-primary(@click.prevent="select(it.abbr)") {{it.name}}
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
/**
|
||||
* This component simply redirects the user to the No Agenda Social authorization page; it is separate here so that it
|
||||
* can be called from two different places, and allow the app to support direct links to authorized content.
|
||||
*/
|
||||
import { computed, Ref, ref } from "vue"
|
||||
import api, { Instance } from "@/api"
|
||||
|
||||
import LoadData from "@/components/LoadData.vue"
|
||||
|
||||
/** The instances configured for Jobs, Jobs, Jobs */
|
||||
const instances : Ref<Instance[]> = ref([])
|
||||
|
||||
/** Whether authorization is in progress */
|
||||
const selected : Ref<Instance | undefined> = ref(undefined)
|
||||
|
||||
/** The authorization URL to which the user should be directed */
|
||||
const authUrl = (() => {
|
||||
/** The client ID for Jobs, Jobs, Jobs at No Agenda Social */
|
||||
const id = "k_06zlMy0N451meL4AqlwMQzs5PYr6g3d2Q_dCT-OjU"
|
||||
const client = `client_id=${id}`
|
||||
const scope = "scope=read:accounts"
|
||||
const redirect = `redirect_uri=${document.location.origin}/citizen/authorized`
|
||||
const respType = "response_type=code"
|
||||
return `https://noagendasocial.com/oauth/authorize?${client}&${scope}&${redirect}&${respType}`
|
||||
})()
|
||||
document.location.assign(authUrl)
|
||||
const authUrl = computed(() => {
|
||||
if (selected.value) {
|
||||
/** The client ID for Jobs, Jobs, Jobs at No Agenda Social */
|
||||
const client = `client_id=${selected.value.clientId}`
|
||||
const scope = "scope=read:accounts"
|
||||
const redirect = `redirect_uri=${document.location.origin}/citizen/${selected.value.abbr}/authorized`
|
||||
const respType = "response_type=code"
|
||||
return `${selected.value.url}/oauth/authorize?${client}&${scope}&${redirect}&${respType}`
|
||||
}
|
||||
return ""
|
||||
})
|
||||
|
||||
/**
|
||||
* Select a given Mastadon instance
|
||||
*
|
||||
* @param abbr The abbreviation of the instance being selected
|
||||
*/
|
||||
const select = (abbr : string) => {
|
||||
selected.value = instances.value.find(it => it.abbr === abbr)
|
||||
document.location.assign(authUrl.value)
|
||||
}
|
||||
|
||||
/** Load the instances we have configured */
|
||||
const retrieveInstances = async (errors : string[]) => {
|
||||
const instancesResp = await api.instances.all()
|
||||
if (typeof instancesResp === "string") {
|
||||
errors.push(instancesResp)
|
||||
} else if (typeof instancesResp === "undefined") {
|
||||
errors.push("No instances found (this should not happen)")
|
||||
} else {
|
||||
instances.value = instancesResp
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user