Skip to content

[WIP] Solid client authn upgrade #350

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
2,344 changes: 1,635 additions & 709 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,14 @@
"homepage": "https://github.com/solid/solid-ui",
"dependencies": {
"@babel/runtime": "^7.10.5",
"@inrupt/solid-client-authn-browser": "^0.3.0",
"escape-html": "^1.0.3",
"jss": "^10.3.0",
"jss-preset-default": "^10.3.0",
"mime-types": "^2.1.27",
"node-uuid": "^1.4.7",
"pane-registry": "^2.2.1",
"rdflib": "^1.3.1",
"solid-auth-client": "^2.5.0",
"solid-namespace": "^0.4.0"
},
"devDependencies": {
Expand Down
22 changes: 22 additions & 0 deletions src/authn/authSession.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {
Session,
getClientAuthenticationWithDependencies
} from '@inrupt/solid-client-authn-browser'

let authSession
// @ts-ignore
if (!window.authSession) {
authSession = new Session(
{
clientAuthentication: getClientAuthenticationWithDependencies({})
},
'mySession'
)
// @ts-ignore
window.authSession = authSession
} else {
// @ts-ignore
authSession = window.authSession
}

export default authSession
161 changes: 93 additions & 68 deletions src/authn/authn.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@
*/
import Signup from './signup'
import widgets from '../widgets'
import solidAuthClient from 'solid-auth-client'
import ns from '../ns.js'
import kb from '../store.js'
import utils from '../utils.js'
import { alert } from '../log'
import authSession from './authSession'
import { AppDetails, AuthenticationContext } from './types'
import { PaneDefinition } from 'pane-registry'
import * as debug from '../debug'
Expand All @@ -34,7 +34,7 @@ import { textInputStyle, buttonStyle, commentStyle } from '../style'
// eslint-disable-next-line camelcase
import { Quad_Object } from 'rdflib/lib/tf-types'

export { solidAuthClient }
export { authSession }

// const userCheckSite = 'https://databox.me/'

Expand Down Expand Up @@ -96,16 +96,10 @@ export function defaultTestUser (): NamedNode | null {
* @returns Named Node or null
*/
export function currentUser (): NamedNode | null {
const str = localStorage['solid-auth-client']
if (str) {
const da = JSON.parse(str)
if (da.session && da.session.webId) {
// @@ TODO check has not expired
return sym(da.session.webId)
}
if (authSession.info.webId) {
return sym(authSession.info.webId)
}
return offlineTestID() // null unless testing
// JSON.parse(localStorage['solid-auth-client']).session.webId
return offlineTestID()
}

/**
Expand Down Expand Up @@ -276,6 +270,7 @@ export function logInLoadPreferences (context: AuthenticationContext): Promise<A
m2 = `Strange: Error ${status} trying to read your preference file.${message}`
alert(m2)
}
resolve(context)
}) // load preference file then
})
.catch(err => {
Expand Down Expand Up @@ -929,11 +924,10 @@ function signInOrSignUpBox (
signInPopUpButton.setAttribute('value', 'Log in')
signInPopUpButton.setAttribute('style', `${signInButtonStyle}background-color: #eef;`)

signInPopUpButton.addEventListener('click', () => {
const offline = offlineTestID()
if (offline) return setUserCallback(offline.uri)
return solidAuthClient.popupLogin().then(session => {
const webIdURI = session.webId
authSession.onLogin(() => {
const sessionInfo = authSession.info
if (sessionInfo && sessionInfo.isLoggedIn) {
const webIdURI = sessionInfo.webId
// setUserCallback(webIdURI)
const divs = dom.getElementsByClassName(magicClassName)
debug.log(`Logged in, ${divs.length} panels to be serviced`)
Expand All @@ -954,6 +948,23 @@ function signInOrSignUpBox (
}
}
}
}
})

signInPopUpButton.addEventListener('click', () => {
const offline = offlineTestID()
if (offline) return setUserCallback(offline.uri)

const thisUrl = new URL(window.location.href).origin
// HACK solid-client-authn-js no longer comes with its own UI for selecting
// an IDP. This was the easiest way to get the user to select.
// TODO: make a nice UI to select an IDP
const issuer = prompt('Enter an issuer', thisUrl)
authSession.login({
// @ts-ignore this library requires a specific kind of URL that isn't global
redirectUrl: window.location.href,
// @ts-ignore
oidcIssuer: issuer
})
}, false)

Expand All @@ -977,8 +988,8 @@ function signInOrSignUpBox (
/**
* @returns {Promise<string|null>} Resolves with WebID URI or null
*/
function webIdFromSession (session?: { webId: string }): string | null {
const webId = session ? session.webId : null
function webIdFromSession (session?: { webId?: string }): string | null {
const webId = session?.webId ? session.webId : null
if (webId) {
saveUser(webId)
}
Expand All @@ -995,42 +1006,59 @@ function checkCurrentUser () {
}
*/

// HACK this global variable exists to prevent authSession.handleIncomingRedirect
// From being called twice. It would not be needed if it automatically redirected
// by iteself. See https://github.com/inrupt/solid-client-authn-js/issues/514
var checkingRedirect = false

/**
* Retrieves currently logged in webId from either
* defaultTestUser or SolidAuthClient
* defaultTestUser or SolidAuthn
* @param [setUserCallback] Optional callback
*
* @returns Resolves with webId uri, if no callback provided
*/
export function checkUser<T> (
export async function checkUser<T> (
setUserCallback?: (me: NamedNode | null) => T
): Promise<NamedNode | T> {
): Promise<NamedNode | T | null> {
/**
* Handle a successful authentication redirect
*/
// HACK normally you wouldn't need to do a check to see if 'code' is in the
// query, but it was removed from solid-client-authn-js
// See https://github.com/inrupt/solid-client-authn-js/issues/421
// Remove this after
const authCode = new URL(window.location.href).searchParams.get('code')
if (authCode && !checkingRedirect) {
checkingRedirect = true
// Being redirected after requesting a token
await authSession
.handleIncomingRedirect(window.location.href)
// HACK solid-client-authn-js should automatically remove code and state
// from the URL, but it doesn't, so we do it manually here
// see https://github.com/inrupt/solid-client-authn-js/issues/514
const newPageUrl = new URL(window.location.href)
newPageUrl.searchParams.delete('code')
newPageUrl.searchParams.delete('state')
window.history.replaceState({}, '', newPageUrl.toString())
}

// Check to see if already logged in / have the WebID
const me = defaultTestUser()
let me = defaultTestUser()
if (me) {
return Promise.resolve(setUserCallback ? setUserCallback(me) : me)
}

// doc = kb.any(doc, ns.link('userMirror')) || doc
const webId = webIdFromSession(authSession.info)

return solidAuthClient
.currentSession()
.then(webIdFromSession)
.catch(err => {
debug.log('Error fetching currentSession:', err)
})
.then(webId => {
// if (webId.startsWith('dns:')) { // legacy rww.io pseudo-users
// webId = null
// }
const me = saveUser(webId)
me = saveUser(webId)

if (me) {
debug.log(`(Logged in as ${me} by authentication)`)
}
if (me) {
debug.log(`(Logged in as ${me} by authentication)`)
}

return setUserCallback ? setUserCallback(me) : me
})
return Promise.resolve(setUserCallback ? setUserCallback(me) : me)
}

/**
Expand Down Expand Up @@ -1069,7 +1097,7 @@ export function loginStatusBox (

function logoutButtonHandler (_event) {
// UI.preferences.set('me', '')
solidAuthClient.logout().then(
authSession.logout().then(
function () {
const message = `Your WebID was ${me}. It has been forgotten.`
me = null
Expand Down Expand Up @@ -1108,40 +1136,37 @@ export function loginStatusBox (
}

box.refresh = function () {
solidAuthClient.currentSession().then(
session => {
if (session && session.webId) {
me = sym(session.webId)
} else {
me = null
}
if ((me && box.me !== me.uri) || (!me && box.me)) {
widgets.clearElement(box)
if (me) {
box.appendChild(logoutButton(me, options))
} else {
box.appendChild(signInOrSignUpBox(dom, setIt, options))
}
}
box.me = me ? me.uri : null
},
err => {
alert(`loginStatusBox: ${err}`)
const sessionInfo = authSession.info
if (sessionInfo && sessionInfo.webId) {
me = sym(sessionInfo.webId)
} else {
me = null
}
if ((me && box.me !== me.uri) || (!me && box.me)) {
widgets.clearElement(box)
if (me) {
box.appendChild(logoutButton(me, options))
} else {
box.appendChild(signInOrSignUpBox(dom, setIt, options))
}
)
}
box.me = me ? me.uri : null
}

if (solidAuthClient.trackSession) {
solidAuthClient.trackSession(session => {
if (session && session.webId) {
me = sym(session.webId)
} else {
me = null
}
box.refresh()
})
function trackSession () {
const sessionInfo = authSession.info
if (sessionInfo && sessionInfo.webId) {
me = sym(sessionInfo.webId)
} else {
me = null
}
box.refresh()
}

trackSession()
authSession.onLogin(trackSession)
authSession.onLogout(trackSession)

box.me = '99999' // Force refresh
box.refresh()
return box
Expand Down
4 changes: 2 additions & 2 deletions src/authn/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
selectWorkspace,
setACLUserPublic,
saveUser,
solidAuthClient
authSession
} from './authn'

export const authn = {
Expand All @@ -48,5 +48,5 @@ export const authn = {
selectWorkspace,
setACLUserPublic,
saveUser,
solidAuthClient
authSession
}
13 changes: 8 additions & 5 deletions src/header/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
work in solid-ui by adjusting where imported functions are found.
*/
import { IndexedFormula, NamedNode, sym } from 'rdflib'
import { loginStatusBox, solidAuthClient } from '../authn/authn'
import { loginStatusBox, authSession } from '../authn/authn'
import { widgets } from '../widgets'
import { emptyProfile } from './empty-profile'
import { addStyleClassToElement, getPod, throttle } from './headerHelpers'
Expand Down Expand Up @@ -75,14 +75,17 @@ export async function initHeader (store: IndexedFormula, options?: HeaderOptions
}

const pod = getPod()
solidAuthClient.trackSession(rebuildHeader(header, store, pod, options))
rebuildHeader(header, store, pod, options)()
authSession.onLogout(rebuildHeader(header, store, pod, options))
authSession.onLogin(rebuildHeader(header, store, pod, options))
}
/**
* @ignore exporting this only for the unit test
*/
export function rebuildHeader (header: HTMLElement, store: IndexedFormula, pod: NamedNode, options?: HeaderOptions) {
return async (session: SolidSession | null) => {
const user = session ? sym(session.webId) : null
return async () => {
const sessionInfo = authSession.info
const user = sessionInfo.webId ? sym(sessionInfo.webId) : null
header.innerHTML = ''
header.appendChild(await createBanner(store, pod, user, options))
}
Expand Down Expand Up @@ -167,7 +170,7 @@ export async function createUserMenu (store: IndexedFormula, user: NamedNode, op
}
}

loggedInMenuList.appendChild(createUserMenuItem(createUserMenuButton('Log out', () => solidAuthClient.logout())))
loggedInMenuList.appendChild(createUserMenuItem(createUserMenuButton('Log out', () => authSession.logout())))

const loggedInMenu = document.createElement('nav')

Expand Down
13 changes: 11 additions & 2 deletions src/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,20 @@
//

import * as debug from './debug'
import { fetch } from 'solid-auth-client'
import authSession from './authn/authSession'

var rdf = require('rdflib')
var store = (module.exports = rdf.graph()) // Make a Quad store
rdf.fetcher(store, { fetch }) // Attach a web I/O module, store.fetcher

const fetcher = async (url, requestInit) => {
if (authSession.info.webId) {
return authSession.fetch(url, requestInit)
} else {
return window.fetch(url, requestInit)
}
}

rdf.fetcher(store, { fetch: fetcher }) // Attach a web I/O module, store.fetcher
store.updater = new rdf.UpdateManager(store) // Add real-time live updates store.updater

debug.log('Unique quadstore initialized.')
Expand Down
7 changes: 4 additions & 3 deletions test/unit/authn/authn.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import {
selectWorkspace,
setACLUserPublic,
saveUser,
solidAuthClient
authSession
} from '../../../src/authn/authn'
import { Session } from '@inrupt/solid-client-authn-browser'
import { AppDetails, AuthenticationContext } from '../../../src/authn/types'
import { sym } from 'rdflib'

Expand Down Expand Up @@ -234,8 +235,8 @@ describe('saveUser', () => {
})
})

describe('solidAuthClient', () => {
describe('authSession', () => {
it('exists', () => {
expect(solidAuthClient).toBeInstanceOf(Object)
expect(authSession).toBeInstanceOf(Session)
})
})
Loading