SAML; Dashboard

This commit is contained in:
garrettmills
2020-05-03 20:16:54 -05:00
parent e3ecfb0d37
commit c389e151b5
1778 changed files with 148410 additions and 82 deletions

View File

@@ -4,7 +4,7 @@ class ActionService {
async perform({ text, action, ...args }) {
if ( action === 'redirect' ) {
if ( args.next ) {
return location_service.redirect(args.next, 1500)
return location_service.redirect(args.next, args.delay || 1500)
}
} else {
throw new TypeError(`Unknown action type: ${action}`)

View File

@@ -4,10 +4,10 @@ class AuthAPI {
return result && result.data && result.data.data && result.data.data.is_valid
}
async attempt({ username, password, create_session }) {
async attempt({ username, password, create_session, ...others }) {
try {
const result = await axios.post('/api/v1/auth/attempt', {
username, password, create_session
username, password, create_session, ...others
})
if ( result && result.data && result.data.data && result.data.data ) {
@@ -32,6 +32,30 @@ class AuthAPI {
const result = await axios.post('/api/v1/auth/mfa/enable')
return result && result.data && result.data.data && result.data.data.success && result.data.data.mfa_enabled
}
async mfa_disable() {
const result = await axios.post('/api/v1/auth/mfa/disable')
return result && result.data && result.data.data && result.data.data.success && !result.data.data.mfa_enabled
}
async has_mfa() {
const result = await axios.get('/api/v1/auth/mfa/enable/date')
if ( result && result.data && result.data.data ) return result.data.data
}
async app_passwords() {
const result = await axios.get('/api/v1/password/app_passwords')
if ( result && result.data && Array.isArray(result.data.data) ) return result.data.data
}
async create_app_password(name) {
const result = await axios.post('/api/v1/password/app_passwords', { name })
if ( result && result.data && result.data.data ) return result.data.data
}
async delete_app_password(uuid) {
await axios.delete(`/api/v1/password/app_passwords/${uuid}`)
}
}
const auth_api = new AuthAPI()

View File

@@ -0,0 +1,37 @@
class Event {
firings = []
subscriptions = []
constructor(name) {
this.name = name
}
subscribe(handler) {
if ( typeof handler !== 'function' ) {
throw new TypeError('Event subscription handlers must be functions.')
}
this.subscriptions.push(handler)
}
async fire(...args) {
this.firings.push({ args })
return Promise.all(this.subscriptions.map(x => x(...args)))
}
}
class EventBusService {
_events = {}
event(name) {
if ( !this._events[name] ) {
this._events[name] = new Event(name)
}
return this._events[name]
}
}
const event_bus = new EventBusService()
export { event_bus, Event }

View File

@@ -11,6 +11,15 @@ class LocationService {
async back() {
return window.history.back()
}
async reload(delay = 0) {
return new Promise(res => {
setTimeout(() => {
window.location.reload()
res()
}, delay)
})
}
}
const location_service = new LocationService()

View File

@@ -0,0 +1,56 @@
import { event_bus } from './EventBus.service.js'
class MessageService {
listener_interval = 25000
alert({type, message, timeout = 0, on_dismiss = () => {} }) {
event_bus.event('message.alert').fire({ type, message, timeout, on_dismiss })
}
modal({title, message, buttons = [] }) {
event_bus.event('message.modal').fire({ title, message, buttons })
}
async fetch() {
const result = await axios.get('/api/v1/message/banners')
if ( result && result.data && result.data.data ) return result.data.data
}
async dismiss(banner_id) {
return axios.post(`/api/v1/message/banners/read/${banner_id}`)
}
init_listener() {
this.message_ids = []
this.listener = setInterval(() => this._listener_tick(), this.listener_interval)
window.addEventListener('beforeunload', () => this.stop_listener())
this._listener_tick()
}
async _listener_tick() {
const result = await this.fetch()
if ( result ) {
for ( const banner of result ) {
if ( this.message_ids.includes(banner.id) ) continue
this.message_ids.push(banner.id)
await this.alert({
type: banner.type,
message: banner.message,
on_dismiss: (e) => {
this.dismiss(banner.id).then(() => {
this.message_ids = this.message_ids.filter(x => x !== banner.id)
})
}
})
}
}
}
stop_listener() {
clearInterval(this.listener)
}
}
const message_service = new MessageService()
export { message_service }

View File

@@ -0,0 +1,13 @@
class PasswordService {
async get_resets() {
const result = await axios.get('/api/v1/password/resets')
if ( result && result.data && result.data.data ) return result.data.data
}
async reset(password) {
await axios.post('/api/v1/password/resets', { password })
}
}
const password_service = new PasswordService()
export { password_service }

View File

@@ -0,0 +1,15 @@
class ProfileService {
async get_profile(user_id = 'me') {
const results = await axios.get(`/api/v1/profile/${user_id}`)
if ( results && results.data && results.data.data ) return results.data.data
}
async update_profile({ user_id, first_name, last_name, email, tagline = undefined }) {
await axios.patch(`/api/v1/profile/${user_id}`, { first_name, last_name, email, tagline })
}
}
const profile_service = new ProfileService()
export { profile_service }

View File

@@ -0,0 +1,32 @@
class Session {
data = {}
init(data) {
this.data = data
}
get(key) {
const parts = key.split('.')
let value = this.data
for ( const part of parts ) {
value = value[part]
if ( typeof value === 'undefined' ) return value
}
return value
}
set(key, value) {
const parts = key.split('.')
let parent = this.data
for ( const part of parts.slice(0, -1) ) {
if ( !parent[part] ) parent[part] = {}
parent = parent[part]
}
parent[parts.reverse()[0]] = value
}
}
const session = new Session()
export { session }

View File

@@ -0,0 +1,24 @@
class UtilityService {
_debounce_timeouts = {}
uuid() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
)
}
debounce(handler = () => {}, delay = 500) {
let timeout = null
return (...args) => {
clearTimeout(timeout)
timeout = setTimeout(() => {
handler(...args)
}, delay)
}
}
}
const utility = new UtilityService()
export { utility }