Compare commits

..

14 Commits

21 changed files with 1012 additions and 12083 deletions

View File

@ -1,5 +0,0 @@
module.exports = {
presets: [
'@vue/app'
]
}

9
web/deploy.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
echo "Building site..."
yarn build
echo "Uploading site..."
duck -y --username sso-tools --upload ftps://uk.storage.bunnycdn.com/ dist/*
echo "Clearing CDN cache..."
curl -X POST -H "AccessKey: $BUNNY_PERSONAL" https://api.bunny.net/pullzone/782714/purgeCache
echo "Done."

View File

@ -4,7 +4,7 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>icon.png">
<link rel="shortcut icon" href="/icon.png">
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900|Material+Icons" rel="stylesheet">
<title>SSO Tools</title>
</head>
@ -13,7 +13,7 @@
<strong>We're sorry but SSO Tools doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
<script type="module" src="/src/main.js"></script>
<script async defer data-website-id="d91e0265-3ca4-4ecb-a1d9-fc4e4f97d3bb" src="https://u.wilw.dev/umami.js"></script>
</body>
</html>

View File

@ -3,47 +3,21 @@
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"predeploy": "yarn build",
"deploy": "aws s3 sync dist/ s3://sso.tools"
"start": "vite --port 3300",
"build": "vite build"
},
"dependencies": {
"core-js": "^2.6.5",
"moment": "^2.29.3",
"vue": "^2.6.10",
"vue-router": "^3.0.6",
"vuetify": "^1.5.13",
"vuex": "^3.1.0"
"@mdi/font": "^7.0.96",
"moment": "^2.29.4",
"vue": "^3.2.45",
"vue-router": "^4.1.6",
"vuetify": "^3.0.1",
"vuex": "^4.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.7.0",
"@vue/cli-plugin-eslint": "^3.7.0",
"@vue/cli-service": "^3.7.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.16.0",
"eslint-plugin-vue": "^5.0.0",
"vue-template-compiler": "^2.5.21"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"parserOptions": {
"parser": "babel-eslint"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
"@vitejs/plugin-vue": "^3.2.0",
"vite": "^3.2.4",
"vue-template-compiler": "^2.7.14"
},
"browserslist": [
"> 1%",

View File

@ -1,68 +1,59 @@
<template>
<div id="app">
<v-app>
<v-app class="d-flex flex-row">
<v-toolbar >
<router-link :to="loggedIn ? '/dashboard' : '/'">
<img :src="logo" style="height:50px;"/>
</router-link>
<v-spacer></v-spacer>
<v-toolbar-items class="hidden-sm-and-down">
<v-btn icon to="/dashboard"><v-icon>explore</v-icon></v-btn>
<div class="d-flex justify-space-between align-center pl-4 pr-4 pt-2 pb-2 bg-indigo-lighten-5">
<div>
<router-link :to="loggedIn ? '/dashboard' : '/'">
<img :src="logo" style="height:50px;"/>
</router-link>
</div>
<div class="d-none d-sm-flex align-center">
<div>
<v-btn flat to="/dashboard" class="mr-4" color="primary" variant='outline'>Dashboard</v-btn>
<v-btn color='indigo-lighten-2' flat icon v-if="loggedIn">
<v-icon icon="mdi-account" />
<v-menu activator="parent">
<v-list>
<v-list-item>
<v-list-item-content>
<v-list-item-title>{{user.firstName}} {{user.lastName}}</v-list-item-title>
<v-list-item-sub-title class="text-grey">{{user.email}}</v-list-item-sub-title>
</v-list-item-content>
</v-list-item>
</v-list>
<v-menu offset-y transition="slide-y-transition" v-if="user">
<template v-slot:activator="{ on }">
<v-btn icon v-on="on"><v-icon>person</v-icon></v-btn>
</template>
<v-card>
<v-list>
<v-list-tile avatar>
<v-list-tile-avatar color="teal">
<span class="white--text headline">{{user.firstName[0].toUpperCase()}}</span>
</v-list-tile-avatar>
<v-divider></v-divider>
<v-list-tile-content>
<v-list-tile-title>{{user.firstName}} {{user.lastName}}</v-list-tile-title>
<v-list-tile-sub-title>{{user.email}}</v-list-tile-sub-title>
</v-list-tile-content>
</v-list-tile>
</v-list>
<v-list>
<v-list-item to='/account'>
<v-list-item-icon><v-icon icon="mdi-cogs" class="mr-2"/></v-list-item-icon>
<v-list-item-content>Account</v-list-item-content>
</v-list-item>
<v-list-item @click="logout">
<v-list-item-icon><v-icon icon="mdi-power" class="mr-2"/></v-list-item-icon>
<v-list-item-content>Logout</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
<v-divider></v-divider>
<v-btn class="mr-2" flat v-on:click="openLogin" v-if="!loggedIn">Login</v-btn>
<v-btn color="teal" dark v-on:click="openRegister" v-if="!loggedIn">Create a free account</v-btn>
</div>
</div>
</div>
<v-list>
<v-list-tile to='/account'>
<v-list-tile-action><v-icon>settings</v-icon></v-list-tile-action>
<v-list-tile-title>Account</v-list-tile-title>
</v-list-tile>
<v-list-tile @click="logout">
<v-list-tile-action>
<v-icon>power_settings_new</v-icon>
</v-list-tile-action>
<v-list-tile-title>Logout</v-list-tile-title>
</v-list-tile>
</v-list>
</v-card>
</v-menu>
<v-btn flat v-on:click="openLogin" v-if="!loggedIn">Login</v-btn>
<v-btn color="teal" dark v-on:click="openRegister" v-if="!loggedIn">Create a free account</v-btn>
</v-toolbar-items>
</v-toolbar>
<div style="min-height:100vh;">
<div class="flex-grow-1">
<router-view></router-view>
</div>
<v-footer dark height="auto">
<v-card class="flex" flat tile>
<v-card-title class="teal">
<img :src="logoLight" style="height:50px;"/>
<v-spacer></v-spacer>
<v-btn flat dark to='/privacy'>Privacy Policy</v-btn>
<v-btn flat dark to="terms">Terms of Use</v-btn>
</v-card-title>
</v-card>
<v-footer class="bg-indigo-lighten-1 mt-10 d-block d-sm-flex justify-space-between flex-grow-0">
<img :src="logoLight" style="height:50px;"/>
<div>
<v-btn flat dark to='/privacy' class="mr-2">Privacy Policy</v-btn>
<v-btn flat dark to="/terms">Terms of Use</v-btn>
</div>
</v-footer>
<v-dialog v-model="registerOpen" persistent max-width="600px">
@ -71,42 +62,38 @@
<span class="headline">Create a free account</span>
</v-card-title>
<v-card-text>
<p>Welcome to SSO Tools!</p>
<h4>Welcome to SSO Tools!</h4>
<p>Registering takes less than a minute and we'll automatically associate the IDPs and other settings you've already setup with your new account.</p>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="First name" required autofocus v-model="newUser.firstName"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Last name" v-model="newUser.lastName" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field type="email" label="Email address" v-model="newUser.email" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field :type="showPassword ? 'text' : 'password'" label="Password" v-model="newUser.password" :append-icon="showPassword ? 'visibility' : 'visibility_off'" @click:append="toggleShowPassword"/>
</v-flex>
</v-layout>
<v-card>
<v-card-text>
<p>We collect this information from you for the sole purpose of creating and maintaining your account, and it will not be used for marketing purposes without your consent. Our Privacy Policy describes how we process your data in more detail.</p>
<v-btn to="/privacy" @click="closeRegister">Privacy Policy</v-btn>
<div class="d-flex mt-5">
<v-text-field class="mr-2" label="First name" required autofocus v-model="newUser.firstName"/>
<v-text-field label="Last name" v-model="newUser.lastName" />
</div>
<div class="d-flex mt-5">
<v-text-field class="mr-2" type="email" label="Email address" v-model="newUser.email" />
<v-text-field :type="showPassword ? 'text' : 'password'" hint="At least 8 characters" label="Password" v-model="newUser.password" :append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" @click:append-inner="toggleShowPassword"/>
</div>
<v-card>
<v-card-text>
<p>We collect this information from you for the sole purpose of creating and maintaining your account, and it will not be used for marketing purposes without your consent. Our Privacy Policy describes how we process your data in more detail.</p>
<div class="d-flex mt-5">
<v-btn class="mr-2" to="/privacy" @click="closeRegister">Privacy Policy</v-btn>
<v-btn to="terms" @click="closeRegister">Terms of Use</v-btn>
<v-checkbox v-model="newUser.termsAgreed" label="I have read and I agree to the SSO Tools Privacy Policy and Terms of Use" required></v-checkbox>
</v-card-text>
</v-card>
</div>
<p class="mt-5">Please indicate below that you have read and you agree to the Privacy Policy and Terms of Use (the "terms").</p>
<v-checkbox v-model="newUser.termsAgreed" label="I have read and agree to the terms" required></v-checkbox>
</v-card-text>
</v-card>
<v-alert :value="registerError" type="error">
<h4>Unable to register this account</h4>
<p>{{registerError}}</p>
</v-alert>
</v-container>
<v-alert class="mt-5" v-if="registerError" type="error">
<h4>Unable to register this account</h4>
<p>{{registerError}}</p>
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" flat @click="closeRegister">Cancel</v-btn>
<v-btn @click="closeRegister">Cancel</v-btn>
<v-btn color="primary" @click="register" :loading="registering">Register</v-btn>
</v-card-actions>
</v-card>
@ -119,44 +106,40 @@
</v-card-title>
<v-card-text>
<p>Welcome back!</p>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field type="email" label="Email address" v-model="loginData.email" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field type="password" label="Password" @keyup.enter="login" v-model="loginData.password" />
</v-flex>
</v-layout>
<div class="d-flex mt-5">
<v-text-field class="mr-2" type="email" label="Email address" v-model="loginData.email" />
<v-text-field type="password" label="Password" @keyup.enter="login" v-model="loginData.password" />
</div>
<div class="d-flex justify-end mb-5">
<v-btn @click="forgotPassword" flat>Forgotten your password?</v-btn>
</div>
<v-alert v-if="loginError" type="error">
<h4>Unable to login</h4>
<p>{{loginError}}</p>
</v-alert>
<v-btn @click="forgotPassword" flat style="float:right;">Forgotten your password?</v-btn>
<div style="clear:both;" />
<v-alert :value="loginError" type="error">
<h4>Unable to login</h4>
<p>{{loginError}}</p>
</v-alert>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" flat @click="closeLogin">Cancel</v-btn>
<v-btn @click="closeLogin">Cancel</v-btn>
<v-btn color="primary" @click="login" :loading="loggingIn">Login</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<v-dialog v-model="forgottenPasswordOpen" persistent max-width="300px">
<v-dialog v-model="forgottenPasswordOpen" persistent max-width="500px">
<v-card>
<v-card-title>
<span class="headline">Forgotten your SSO Tools password?</span>
</v-card-title>
<v-card-text>
<p>No problem. Enter the email address of your account below, and if it exists we'll send a password-reset link to you.</p>
<v-text-field type="email" label="Email address" v-model="loginData.email" />
<v-text-field class="mt-5" type="email" label="Email address" v-model="loginData.email" />
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="secondary" flat @click="forgottenPasswordOpen = false">Cancel</v-btn>
<v-btn @click="forgottenPasswordOpen = false">Cancel</v-btn>
<v-btn color="primary" @click="resetPassword" :loading="resettingPassword">Send me a reset link</v-btn>
</v-card-actions>
</v-card>

BIN
web/src/assets/empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

BIN
web/src/assets/login.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -1,28 +1,22 @@
<template>
<v-container>
<h1 style="margin-bottom: 20px;">Your account</h1>
<h1>Your account</h1>
<v-card style="margin-bottom: 20px;">
<v-card class="mt-10">
<v-card-title><span class="headline">About you</span></v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="First name" v-model="user.firstName" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Last name" v-model="user.lastName" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Email address" v-model="user.email"/>
</v-flex>
<p>Please note that if you change your email address we will notify both your old and new addresses.</p>
</v-layout>
<v-alert :value="error" type="error">
<h4>Unable to update your profile</h4>
<p>{{error}}</p>
</v-alert>
</v-container>
<div class="d-flex mt-5">
<v-text-field class="mr-2" label="First name" v-model="user.firstName" />
<v-text-field label="Last name" v-model="user.lastName" />
</div>
<div class="d-flex mt-2">
<v-text-field class="w-50" label="Email address" v-model="user.email"/>
</div>
<p>Please note that if you change your email address we will notify both your old and new addresses.</p>
<v-alert v-if="error" type="error">
<h4>Unable to update your profile</h4>
<p>{{error}}</p>
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
@ -30,28 +24,22 @@
</v-card-actions>
</v-card>
<v-card style="margin-bottom: 20px;">
<v-card class="mt-10">
<v-card-title><span class="headline">Password</span></v-card-title>
<v-card-text>
<v-container grid-list-md>
<p>Change the password associated with your SSO Tools account. This does not affect the accounts of any IDP users you may manage.</p>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field type="password" label="Current password" v-model="currentPassword" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field :type="showPassword ? 'text' : 'password'" label="New password (8+ characters)" v-model="newPassword" :append-icon="showPassword ? 'visibility' : 'visibility_off'" @click:append="showPassword = !showPassword"/>
</v-flex>
</v-layout>
<v-alert :value="passwordError" type="error">
<h4>Unable to change your password</h4>
<p>{{passwordError}}</p>
</v-alert>
<v-alert :value="passwordSuccess" type="success">
<h4>Password updated successfully</h4>
<p>Use your new password next time you login.</p>
</v-alert>
</v-container>
<p>Change the password associated with your SSO Tools account. This does not affect the accounts of any IDP users you may manage.</p>
<div class="d-flex mt-5">
<v-text-field class="mr-2" type="password" label="Current password" v-model="currentPassword" />
<v-text-field :type="showPassword ? 'text' : 'password'" label="New password (8+ characters)" v-model="newPassword" :append-inner-icon="showPassword ? 'mdi-eye' : 'mdi-eye-off'" @click:append-inner="showPassword = !showPassword"/>
</div>
<v-alert v-if="passwordError" type="error">
<h4>Unable to change your password</h4>
<p>{{passwordError}}</p>
</v-alert>
<v-alert v-if="passwordSuccess" type="success">
<h4>Password updated successfully</h4>
<p>Use your new password next time you login.</p>
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
@ -59,22 +47,16 @@
</v-card-actions>
</v-card>
<v-card>
<v-card class="mt-10">
<v-card-title><span class="headline">Delete your account</span></v-card-title>
<v-card-text>
<v-container grid-list-md>
<p>If you delete your SSO Tools account, we'll also delete your account content. This includes Identity Providers, Service Providers, and any IdP users you have created.</p>
<p>To continue, please enter your account password.</p>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field type="password" label="Current password" v-model="deletePassword" />
</v-flex>
</v-layout>
<v-alert :value="deleteError" type="error">
<h4>Unable to delete your account</h4>
<p>{{deleteError}}</p>
</v-alert>
</v-container>
<p>If you delete your SSO Tools account, we'll also delete your account content. This includes Identity Providers, Service Providers, and any IdP users you have created.</p>
<p>To continue, please enter your account password.</p>
<v-text-field class="mt-5" type="password" label="Current password" v-model="deletePassword" />
<v-alert v-if="deleteError" type="error">
<h4>Unable to delete your account</h4>
<p>{{deleteError}}</p>
</v-alert>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>

View File

@ -1,63 +1,58 @@
<template>
<div>
<v-container grid-list-xl :fluid="true">
<v-container fluid="true">
<h1>Welcome back<span v-if="user">, {{user.firstName}}</span></h1>
<v-layout style="margin-top:30px;">
<v-flex xs12 sm6 md4>
<v-alert :value="loggedIn" type="info">
<h4>Thanks for being a member!</h4>
<p>If you need any support with SSO Tools or with connecting applications using SAML2, please get in touch with us.</p>
<div class="d-block d-sm-flex mt-10">
<div class="mr-sm-2 mb-4" style="max-width: 350px">
<v-alert v-if="loggedIn" color="blue-darken-1" icon="mdi-flash">
<h3>Thanks for being a member!</h3>
<p>If you need any support with SSO Tools, or with connecting applications using SAML2, please get in touch with us.</p>
</v-alert>
<v-alert :value="!loggedIn" type="warning" >
<h4>Hey! Listen!</h4>
<p>You're currently using SSO Tools in sandbox mode as a non-member.</p>
<v-alert v-if="!loggedIn" type="warning" >
<h3>Hey! Listen!</h3>
<p class="mb-4">You're currently using SSO Tools in sandbox mode as a non-member.</p>
<p>This is totally fine, but it means that you'll lose access to any IDPs, SPs, and other settings you create if and when your session ends. Login or create a new account to save your progress.</p>
<v-btn color="teal" dark v-on:click="register">Create your free account</v-btn>
<v-btn v-on:click="login">Login</v-btn>
<div class="mt-5">
<v-btn class="mr-2" color="teal" dark v-on:click="register">Register</v-btn>
<v-btn v-on:click="login">Login</v-btn>
</div>
</v-alert>
</v-flex>
<v-flex xs12 sm6 md12>
<v-btn style="float:right;" to='/idps/new' color="primary" v-if="idps.length">Create a new IDP</v-btn>
<h2>Your IDPs</h2>
<div style="clear:both;" />
</div>
<div class="flex-grow-1">
<div style="text-align:center;margin-top:50px;" v-if="loading">
<v-progress-circular indeterminate color="primary" :size="50"></v-progress-circular>
</div>
<div v-if="!idps.length && !loading" style="text-align:center;margin-top:50px;">
<v-sheet elevation="6">
<div style="padding: 20px">
<h4>There's nothing out there yet</h4>
<v-btn to='/idps/new' color="primary">Get started!</v-btn>
<img :src="emptyImage" style="width:100%;max-width:400px;display:block;margin:20px auto;" />
</div>
</v-sheet>
<div v-if="!idps.length && !loading" class="text-center">
<h3 class="mb-5">You don't yet have any IdPs</h3>
<v-btn to='/idps/new' color="primary">Create your first IdP</v-btn>
<img :src="emptyImage" style="width:100%;max-width:400px;display:block;margin:20px auto;" />
</div>
<v-layout row wrap>
<v-flex xs12 sm12 md6 v-for="idp in idps" :key="idp._id" style="padding: 8px;">
<v-card :to="`/idps/${idp._id}`">
<v-card-title primary-title>
<div>
<h3 class="headline mb-0">{{idp.name}}</h3>
<div>https://idp.sso.tools/{{idp.code}}</div>
</div>
</v-card-title>
<v-card-actions>
<v-btn :to="{ path: `/idps/${idp._id}`}" flat color="orange"><v-icon>settings</v-icon> Manage</v-btn>
</v-card-actions>
</v-card>
</v-flex>
</v-layout>
</v-flex>
</v-layout>
<div v-if="idps.length">
<div class="d-flex justify-space-between">
<h2>Your IDPs</h2>
<v-btn to='/idps/new' color="primary" v-if="idps.length">Create a new IDP</v-btn>
</div>
<div class="mt-10 d-flex flex-wrap">
<div class="w-50 pa-2" v-for="idp in idps" :key="idp._id">
<v-card :to="`/idps/${idp._id}`">
<v-card-title primary-title>
<h3 class="headline mb-0">{{idp.name}}</h3>
</v-card-title>
<v-card-text>https://idp.sso.tools/{{idp.code}}</v-card-text>
<v-card-actions>
<v-btn :to="`/idps/${idp._id}`" flat color="primary" prepend-icon="mdi-cog">Manage</v-btn>
</v-card-actions>
</v-card>
</div>
</div>
</div>
</div>
</div>
</v-container>
</div>
@ -65,7 +60,7 @@
<script>
import api from '../api';
import emptyImage from '../assets/empty.jpg';
import emptyImage from '../assets/empty.png';
export default {
name: 'Dashboard',

View File

@ -1,119 +1,117 @@
<template>
<div>
<div class="banner">
<div class="banner-background" :style="`background-image:url(${landing});`"/>
<div class="banner-content">
<v-container>
<v-layout>
<v-flex xs12 md6>
<h1>Easily set-up and test your single sign-on applications</h1>
<div class="bg-indigo-lighten-5">
<v-container class="d-block d-sm-flex align-center">
<div class="w-auto pa-5">
<h1 class="mb-5">Set-up and test single sign-on in your web, mobile, and desktop apps</h1>
<p>With SSO Tools it is easy to spin-up your own custom identity providers, allowing you to start testing your <span class="bg-yellow-lighten-3 rounded-md pa-1">SAML2</span> applications in minutes.</p>
<v-btn class="mt-5" to="/idps/new" color="primary" prepend-icon="mdi-plus"> Create your own IdP</v-btn>
<div class="d-flex align-center mt-2">
<v-icon icon="mdi-star" class="mr-2 text-primary" />
<p><small>It's free &amp; you don't have to register an account</small></p>
</div>
<div class="d-flex align-center mt-1">
<v-icon icon="mdi-star" class="mr-2 text-primary" />
<p><small>SSO Tools is fully open-source</small></p>
</div>
</div>
<div class="w-auto">
<img v-bind:src="landingImage" class="w-100"/>
</div>
</v-container>
</div>
<h2 style="margin-top:40px;">IdP Manager</h2>
<p>SSO Tools can quickly spin-up your own custom Identity Providers. Start testing your SAML2 applications in minutes.</p>
<v-btn to="/idps/new" color="primary"><v-icon>add</v-icon> Create your own IdP</v-btn>
<p><small>* It's free &amp; you don't need to register an account</small></p>
</v-flex>
</v-layout>
</v-container>
<v-container>
<div class="text-center mt-10 ml-5 mr-5">
<h2>Get confident with your SSO apps</h2>
<p>Develop and fully test out your applications with single sign-on to radically improve your offering for <strong>enterprise customers</strong>.</p>
<p>SSO Tools is great for small SaaS organisations who need to learn about and <strong>provide federated authentication</strong> to their customers.</p>
</div>
<div class="d-block d-sm-flex ml-5 ml-sm-16 mr-5 mr-sm-16 mt-16">
<div class="w-auto mr-5 mb-5">
<h2>Quick and simple: we focus on the essentials</h2>
<p>Use the SSO Tools sandbox to quickly spin-up identity providers, register users and set-up your service providers in order to test the entire SSO lifecycle.</p>
<p>You don't need to worry about signing up for complicated SSO providers, and <strong>you don't even need an account to get started</strong>.</p>
</div>
<div class="w-auto">
<img :src="usersImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</div>
</div>
<v-container grid-list-xl>
<v-layout :justify-center="true">
<v-flex xs12 sm8 md6 lg4 style="text-align:center;">
<h2>Get confident with your SSO apps</h2>
<p>Develop and fully test out your applications with single sign-on to radically improve your offering for <strong>enterprise customers</strong>.</p>
<p>SSO Tools is great for small SaaS organisations who need to learn about and <strong>provide federated authentication</strong> to their customers.</p>
</v-flex>
</v-layout>
<div class="d-block d-sm-flex ml-5 ml-sm-16 mr-5 mr-sm-16 mt-16">
<div class="w-auto">
<img :src="secureImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</div>
<div class="w-auto ml-5 mt-5">
<h2>Personalised and secure</h2>
<p>Each of your identity providers is given its own path at our IdP subdomain, allowing you to easily separate different IdP configurations for testing different functions.</p>
<h2 class="mt-5">Emulate a real-world IdP</h2>
<p>SSO Tools allows you to run a typical IdP environment. Users associated with your provider can locally login to list your connected applications to test the login and logout flows.</p>
<p>Support for both IdP and SP initiated sign-on is available, allowing you to test your applications under typical enterprise SSO usage. For example, if one of your customers has a link to sign-in to your app from their local intranet.</p>
</div>
</div>
<v-layout style="margin-top:50px;" wrap>
<v-flex xs12 md6>
<h2>Quick and simple: we focus on the essentials</h2>
<p>Use the SSO Tools sandbox to quickly spin-up identity providers, register users and set-up your service providers in order to test the entire SSO lifecycle.</p>
<p>You don't need to worry about signing up for complicated SSO providers, and <strong>you don't even need an account to get started</strong>.</p>
</v-flex>
<v-flex xs12 md6>
<img :src="usersImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</v-flex>
</v-layout>
<div class="d-block d-sm-flex ml-5 ml-sm-16 mr-5 mr-sm-16 mt-16">
<div class="w-auto mr-5 mb-5">
<h2>Follow industry standards</h2>
<p>SSO Tools allows you to test your SSO implementations in your apps following protocols and standards used by the vast majority of large companies, educational institutions, government agencies, and more.</p>
<p>We currently support the SAML2 protocol, which works well with your customers using LDAP and/or Microsoft Active Directory. Each IdP you create can have different settings to enable you to fully test out your services and your multi-tenancy support.</p>
</div>
<div class="w-auto">
<img :src="samlImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</div>
</div>
<v-layout style="margin-top:50px;" wrap>
<v-flex xs12 md6 order-xs2 order-md1>
<img :src="secureImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</v-flex>
<v-flex xs12 md6 order-xs1 order-md2>
<h2>Personalised and secure</h2>
<p>Each of your identity providers is given its own path at our IdP subdomain, allowing you to easily separate different IdP configurations for testing different functions.</p>
<h2>Emulate a real-world IdP</h2>
<p>SSO Tools allows you to run a typical IdP environment. Users associated with your provider can locally login to list your connected applications to test the login and logout flows.</p>
<p>Support for both IdP and SP initiated sign-on is available, allowing you to test your applications under typical enterprise SSO usage. For example, if one of your customers has a link to sign-in to your app from their local intranet.</p>
</v-flex>
</v-layout>
<div class="d-flex flex-column align-center bg-indigo-lighten-5 mt-16 pa-10 rounded-lg elevation-5">
<div class="w-75">
<h1>Features &amp; roadmap</h1>
<p>SSO Tools is offered as a free service. For any questions, or to get in touch, please do so via <a href="mailto:hello@sso.tools" target="_blank" rel="noopener noreferrer">email</a>.</p>
</div>
<v-layout style="margin-top:50px;" wrap>
<v-flex xs12 md6>
<h2>Follow industry standards</h2>
<p>SSO Tools allows you to test your SSO implementations in your apps following protocols and standards used by the vast majority of large companies, educational institutions, government agencies, and more.</p>
<p>We currently support the SAML2 protocol, which works well with your customers using LDAP and/or Microsoft Active Directory. Each IdP you create can have different settings to enable you to fully test out your services and your multi-tenancy support.</p>
</v-flex>
<v-flex xs12 md6>
<img :src="samlImage" style="width:100%;border-radius:3px;box-shadow:0px 4px 10px rgba(0,0,0,0.2);" />
</v-flex>
</v-layout>
<h1 style="margin-top:50px;">Features &amp; roadmap</h1>
<p>SSO Tools is currently offered as a free service. It is maintained by a single developer for testing SAML2 SSO applications. It's not recommended for production use as there is no offer of guarantee for uptime! For any questions, or to get in touch, please do so via <a href="mailto:hello@sso.tools" target="_blank" rel="noopener noreferrer">email</a>.</p>
<v-layout wrap>
<v-flex sm6>
<h2>What you get now</h2>
<v-list dense style="margin-top:15px;">
<v-list-tile v-for="feature in features" :key="feature" avatar>
<v-list-tile-avatar>
<v-icon style="color:green;">check_circle</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>{{ feature }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<div class="d-block d-sm-flex mt-10">
<div class="mr-2">
<h3>What you get now</h3>
<v-list>
<v-list-item v-for="feature in features" :key="feature">
<v-list-item-icon><v-icon icon="mdi-check-circle" class="text-green mr-2"/></v-list-item-icon>
<v-list-item-content>
{{ feature }}
</v-list-item-content>
</v-list-item>
</v-list>
</v-flex>
</div>
<v-flex sm6>
<h2>What's coming</h2>
<v-list dense style="margin-top:15px;">
<v-list-tile v-for="item in roadmap" :key="item" avatar>
<v-list-tile-avatar>
<v-icon style="color:rgb(200,200,200);">access_time</v-icon>
</v-list-tile-avatar>
<v-list-tile-content>
<v-list-tile-title>{{ item }}</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<div class="mt-5 mt-sm-0">
<h3>What's coming</h3>
<v-list>
<v-list-item v-for="item in roadmap" :key="item" avatar>
<v-list-item-icon><v-icon icon="mdi-clock-outline" class="text-grey-lighten-1 mr-2" /></v-list-item-icon>
<v-list-item-content>
{{ item }}
</v-list-item-content>
</v-list-item>
</v-list>
</v-flex>
</v-layout>
</div>
</div>
</div>
<div style="margin: 50px 0px;"><v-divider /></div>
<v-layout :justify-center="true">
<v-flex xs12 sm10 md8 lg6>
<h1>👋 Hi there, thanks for visiting</h1>
<p>SSO Tools is created and maintained by a solo developer. During my work as part of a growing startup selling enterprise SaaS, the need for providing larger organisations with federated authentication became hugely important.</p>
<p>I spent a lot of my spare time reading-up and learning about the different approaches to SSO via SAML2, with a focus on security whilst also making set-up easy for our customers.</p>
<p>In my spare time I began working on SSO Tools so that I could easily test sign-on and logout flows across different use-cases.</p>
<p>SSO Tools is free and open-source software, and you can <a href='https://git.wilw.dev/wilw/sso-tools' target='_blank' rel='noopener noreferrer'>check out the source code</a> if you are interested. <strong>I hope you find it useful!</strong></p>
</v-flex>
</v-layout>
<div class="mt-10 d-flex justify-center">
<div class="w-75">
<h1 class="mb-8">👋 Hi there, thanks for visiting</h1>
<p class="mb-2">SSO Tools is created and maintained by a solo developer. During my work as part of a growing startup selling enterprise SaaS, the need for providing larger organisations with federated authentication became hugely important.</p>
<p class="mb-2">I spent a lot of my spare time reading-up and learning about the different approaches to SSO via SAML2, with a focus on security whilst also making set-up easy for our customers.</p>
<p class="mb-2">In my spare time I began working on SSO Tools so that I could easily test sign-on and logout flows across different use-cases.</p>
<p class="mb-2">SSO Tools is free and open-source software, and you can <a href='https://git.wilw.dev/wilw/sso-tools' target='_blank' rel='noopener noreferrer'>check out the source code</a> if you are interested. <strong>I hope you find it useful!</strong></p>
</div>
</div>
</v-container>
</div>
</template>
<script>
import landing from '../assets/landing.png';
import landingImage from '../assets/login.png';
import usersImage from '../assets/users.png';
import secureImage from '../assets/secure.png';
import samlImage from '../assets/saml.png';
@ -122,7 +120,7 @@ export default {
name: 'Home',
data() {
return {
landing, usersImage, secureImage, samlImage,
landingImage, usersImage, secureImage, samlImage,
features: [
'Account-less sandbox', 'Create unlimited IdPs', 'Register apps/SPs with your IdPs', 'Register users with your IdPs', 'Support for custom user attributes', 'Support for specifying custom attribute mappings', 'IdP authentication', 'IdP- and SP-initiated SAML2 sign-on', 'SAML2 ForceAuthn respected', 'SP-initiated log-out'
],
@ -160,7 +158,6 @@ export default {
}
.banner-content{
position:relative;
min-height:300px;
position: relative;
}
</style>

View File

@ -1,79 +1,50 @@
<template>
<div>
<v-container grid-list-lg>
<v-container>
<h3>Manage Identity Provider</h3>
<h1 style="margin-bottom:30px">{{idp && idp.name}}</h1>
<h1 class="mb-10">{{idp && idp.name}}</h1>
<v-layout wrap>
<v-flex md4 xs12>
<v-card>
<v-list>
<v-list-tile exact :to="`/idps/${idp._id}`">
<v-list-tile-action>
<v-icon>home</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Overview</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<div class="d-block d-sm-flex">
<div class="mb-10">
<v-card v-if="idp">
<v-list>
<v-list-item exact :to="`/idps/${idp._id}`" prepend-icon="mdi-home">
<v-list-item-content>Overview</v-list-item-content>
</v-list-item>
<v-list-tile :to="`/idps/${idp._id}/settings`">
<v-list-tile-action>
<v-icon>settings</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Settings</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-item :to="`/idps/${idp._id}/settings`" prepend-icon="mdi-cogs">
<v-list-item-content>Settings</v-list-item-content>
</v-list-item>
<v-list-tile :to="`/idps/${idp._id}/users`">
<v-list-tile-action>
<v-icon>people</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Users</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-item :to="`/idps/${idp._id}/users`" prepend-icon="mdi-account-group">
<v-list-item-content>Users</v-list-item-content>
</v-list-item>
<v-list-tile :to="`/idps/${idp._id}/sps`">
<v-list-tile-action>
<v-icon>extension</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>Connected apps</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-item :to="`/idps/${idp._id}/sps`" prepend-icon="mdi-power-plug">
<v-list-item-content>Connected apps</v-list-item-content>
</v-list-item>
<v-divider></v-divider>
<v-divider></v-divider>
<v-list-tile :to="`/idps/${idp._id}/saml`">
<v-list-tile-action>
<v-icon>compare_arrows</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>SAML2 configuration</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-tile :to="`/idps/${idp._id}/logs`">
<v-list-tile-action>
<v-icon>format_list_bulleted</v-icon>
</v-list-tile-action>
<v-list-tile-content>
<v-list-tile-title>SAML2 logs</v-list-tile-title>
</v-list-tile-content>
</v-list-tile>
<v-list-item :to="`/idps/${idp._id}/saml`" prepend-icon="mdi-swap-horizontal">
<v-list-item-content>SAML2 configuration</v-list-item-content>
</v-list-item>
<v-list-item :to="`/idps/${idp._id}/logs`" prepend-icon="mdi-format-list-bulleted">
<v-list-item-content>SAML2 logs</v-list-item-content>
</v-list-item>
</v-list>
</v-card>
<v-btn block color='secondary' style="margin-top: 15px;" :href="`https://idp.sso.tools/${idp.code}`" target="_blank"><v-icon>open_in_new</v-icon> Open IdP dashboard</v-btn>
</v-flex>
<v-btn block class="mt-5" :href="`https://idp.sso.tools/${idp?.code}`" target="_blank" prepend-icon="mdi-open-in-new">Open IdP dashboard</v-btn>
</div>
<v-flex md8>
<div class="ml-sm-5" style="flex: 1">
<div v-if="idp">
<router-view :idp="idp"/>
<router-view :idp="idp" @onUpdateIdp="updateIdp"/>
</div>
</v-flex>
</v-layout>
</div>
</div>
</v-container>
</div>
@ -96,7 +67,9 @@ export default {
});
},
methods: {
updateIdp(upd) {
this.idp = upd;
},
},
}
</script>

View File

@ -6,7 +6,7 @@
<v-card-text>
<h5>IDP host</h5>
<p>The URL of your IDP on the Internet. This forms the basis of your <router-link :to="`/idps/${idp._id}/saml`">SSO configuration</router-link>.</p>
<v-text-field :value="`https://idp.sso.tools/${idp.code}`" label="IDP host" readonly/>
<v-text-field :model-value="`https://idp.sso.tools/${idp.code}`" label="IDP host" variant="solo" readonly/>
<h5>SAML2 configuration</h5>
<v-btn :to='`/idps/${idp._id}/saml`'>See SAML2 configuration</v-btn>
@ -18,7 +18,7 @@
<v-card-title primary-title><h3>Delete IDP</h3></v-card-title>
<v-card-text>Deleting this IDP will immediately and irreversibly remove the Identity Provider, its registered Service Providers, and any users associated with the IDP.</v-card-text>
<v-card-actions>
<v-btn v-on:click="deleteIDP" color='warning'>Delete</v-btn>
<v-btn v-on:click="deleteIDP" color='red'>Delete</v-btn>
</v-card-actions>
</v-card>
@ -56,7 +56,7 @@ export default {
deleteConfirm (event) {
this.deletingProgress = true;
api.req('DELETE', `/idps/${this.$route.params.id}`, null, resp => {
this.deletingProgress = false;
this.deletingProgress = false;
this.deleting = false;
this.$router.push('/dashboard');
}, err => {

View File

@ -1,19 +1,19 @@
<template>
<div>
<p>Below you'll find the latest requests and responses logged by SSOTools for your IdP. Requests are logged in JSON format and are encoded into XML for SAML2 transport.</p>
<v-alert :value="true" type="info">Checking the logs can be useful in debugging your SSO setup. You can use it to compare against what you are sending or receiving in your application.</v-alert>
<p>Below you'll find the latest requests and responses logged by SSOTools for your IdP. Requests are logged in JSON format and are encoded into XML for SAML2 transport.</p>
<v-alert class="mt-5" type="info">Checking the logs can be useful in debugging your SSO setup. You can use it to compare against what you are sending or receiving in your application.</v-alert>
<v-btn v-on:click="fetchLogs" style="margin: 10px 0px;"><v-icon>refresh</v-icon> Refresh logs</v-btn>
<v-btn v-on:click="fetchLogs" class="mt-10 mb-5" prepend-icon="mdi-refresh">Refresh logs</v-btn>
<v-alert v-if="!logs.length" :value="true" border="left" color="blue-grey" dark>No requests have been logged against this IdP yet.</v-alert>
<v-card v-for="log in logs" :key="log._id" style="padding: 5; margin-bottom: 15px">
<v-card-title primary-title>
<v-layout row wrap style="font-weight: bold;">
<v-flex xs6 sm6 md4>{{formatDate(log.createdAt)}}</v-flex>
<v-flex xs6 sm6 md4>{{log.spName || log.sp}}</v-flex>
<v-flex xs6 sm6 md4>{{log.type}}</v-flex>
</v-layout>
<v-card-title>
<div class="d-flex justify-space-between">
<p class="mr-2">{{formatDate(log.createdAt)}}</p>
<p class="mr-2">{{log.spName || log.sp}}</p>
<p>{{log.type}}</p>
</div>
</v-card-title>
<v-card-text>
<textarea readonly rows="10" style="width: 100%; font-size: 8; font-family: monospace;" v-model="log.formattedData" />

View File

@ -1,13 +1,13 @@
<template>
<div>
<p>You can use the details below to configure your SAML2-compatibile service providers to use this IDP as an identity source.</p>
<v-alert :value="true" type="info">To start using a service provider with this IDP you will also need to register it on the <router-link style="color:white;" :to="`/idps/${idp._id}/sps`">Service Providers</router-link> page.</v-alert>
<p>You can use the details below to configure your SAML2-compatibile service providers to use this IDP as an identity source.</p>
<v-alert type="info" class="mt-5">To start using a service provider with this IDP you will also need to register it on the <router-link style="color:white;" :to="`/idps/${idp._id}/sps`">Connected apps</router-link> page.</v-alert>
<div style="margin-top:20px;" />
<v-text-field readonly label="Sign-on URL (single login service through the HTTP redirect binding)" :value="`https://idp.sso.tools/${idp.code}/saml/login/request`" />
<v-text-field readonly label="Logout URL (single logout service through the HTTP redirect binding)" :value="`https://idp.sso.tools/${idp.code}/saml/logout/request`" />
<v-textarea :value="idp.saml.certificate" label="Signing certificate" readonly rows="6" style="font-size:11px;"/>
<v-alert :value="true" type="info">
<v-text-field readonly label="Sign-on URL (single login service through the HTTP redirect binding)" :model-value="`https://idp.sso.tools/${idp.code}/saml/login/request`" />
<v-text-field readonly label="Logout URL (single logout service through the HTTP redirect binding)" :model-value="`https://idp.sso.tools/${idp.code}/saml/logout/request`" />
<v-textarea :model-value="idp.saml.certificate" label="Signing certificate" readonly rows="6" style="font-size:11px;"/>
<v-alert type="info" class="mt-5">
<h4>Self-signed certificates</h4>
<p>Please note that SSO Tools only issues self-signed certificates. This means that you may need to turn off signature validation on your service provider.</p>
</v-alert>
@ -25,7 +25,7 @@ export default {
}
},
methods: {
},
}
</script>

View File

@ -1,14 +1,11 @@
<template>
<div>
<p>Configure your identity provider.</p>
<div style="clear:both;margin-bottom: 20px;" />
<h5>Friendly name</h5>
<h3>Friendly name</h3>
<p>This is the name given to your IDP so that you can recognise it. It's also the name shown to <router-link :to="`/idps/${idp._id}/users`">users who authenticate against this IDP</router-link>.</p>
<v-text-field v-model="idp.name" label="Friendly name"/>
<h5>Code (issuer)</h5>
<p>A globally unique machine-friendly (i.e. [a-ZA-Z0-9]) code to identify this IDP on SSO Tools. We derive the issuer and other SSO information from this code, so you may need to update other configurations elsewhere if you do change it.</p>
<h3>Code (issuer)</h3>
<p>A globally unique machine-friendly (alphanumeric) code to identify this IDP on SSO Tools. We derive the issuer and other SSO information from this code, so you may need to update other configurations elsewhere if you do change it.</p>
<v-text-field v-model="idp.code" label="Code" />
<v-btn color='primary' :loading="saving" v-on:click="save">Save changes</v-btn>
@ -35,7 +32,7 @@ export default {
const data = { name, code };
this.saving = true;
api.req('PUT', `/idps/${this.idp._id}`, { name, code }, resp => {
this.idp = resp;
this.$emit('onUpdateIdp', resp);
this.snackbar = true;
this.saving = false;
}, err => {

View File

@ -1,74 +1,70 @@
<template>
<div>
<h2>Apps connected to your IdP</h2>
<p>If you are creating a single sign-on facility in your application you can register it as a service provider here in order to test the connection.</p>
<p>Register an app (e.g. your website or mobile app) here in order to connect it to your IdP.</p>
<v-alert :value="true" type="info">Users registered with this IdP will only be able to access the apps regisered below.</v-alert>
<v-alert class="mt-5" icon="mdi-information">Users registered with this IdP will only be able to access the apps listed below.</v-alert>
<v-btn color='primary' style="float:right;" v-on:click="openDialog"><v-icon>add</v-icon> Register a new SP</v-btn>
<div style="clear:both;margin-bottom: 20px;" />
<div class="d-flex justify-end mt-5 mb-b">
<v-btn color='primary' v-on:click="openDialog" prepend-icon="mdi-plus">New app</v-btn>
</div>
<v-data-table :loading="loadingSps" hide-actions class="elevation-1" :headers="tableHeaders" :items="sps">
<template v-slot:items="props">
<td>{{props.item.name}}</td>
<td>
EntityID: {{props.item.entityId}}<br />
Service: {{props.item.serviceUrl}}<br />
Consumer: {{props.item.callbackUrl}}<br />
Logout: {{props.item.logoutUrl}}<br />
Logout callback: {{props.item.logoutCallbackUrl}}
</td>
<td>
<v-menu offset-y transition="slide-y-transition">
<template v-slot:activator="{ on }">
<v-icon small class="mr-2" v-on="on">settings</v-icon>
</template>
<v-list>
<v-list-tile v-on:click="e => editSp(props.item)">
<v-list-tile-title><v-icon>edit</v-icon> Update</v-list-tile-title>
</v-list-tile>
<v-list-tile v-on:click="e => deleteSp(props.item._id)">
<v-list-tile-title><v-icon>delete</v-icon> Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</td>
</template>
</v-data-table>
<v-table :items="sps" v-if="sps.length">
<thead>
<tr>
<th>App name</th>
<th>Configuration</th>
<th />
</tr>
</thead>
<tbody>
<tr v-for="app in sps" key="app._id">
<td>{{app.name}} <br />
<span class="text-grey">{{app.serviceUrl}}</span>
</td>
<td>
<span v-if="app.entityId">EntityID: {{app.entityId}}</span>
<span v-if="app.callbackUrl"><br />Consumer: {{app.callbackUrl}}</span>
<span v-if="app.logoutUrl"><br />Logout: {{app.logoutUrl}}</span>
<span v-if="app.logoutCallbackUrl"><br />Logout callback: {{app.logoutCallbackUrl}}</span>
</td>
<td>
<v-btn flat>
Manage
<v-menu activator="parent">
<v-list>
<v-list-item v-on:click="e => editSp(app)" prepend-icon="mdi-pencil">
<v-list-item-content>Update</v-list-item-content>
</v-list-item>
<v-list-item v-on:click="e => deleteSp(app._id)" prepend-icon="mdi-delete">
<v-list-item-content>Delete</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</td>
</tr>
</tbody>
</v-table>
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline">{{editing ? 'Edit Service Provider': 'Register a Service Provider for use with this IDP'}}</span>
<span class="headline">{{editing ? 'Edit app': 'Register an app for use with this IDP'}}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="Friendly name" required autofocus v-model="newSP.name" hint="To help you identify this SP." placeholder="My Service"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Service URL" v-model="newSP.serviceUrl" hint="The URL used to access your service. For example, for a webapp, you can just use your website URL." placeholder="https://sp.example.com"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="EntityID" v-model="newSP.entityId" hint="This is a URL to uniquely identify your service. It is sometimes the same as the metadata URL." placeholder="https://sp.example.com/metdadata"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="ACS URL" v-model="newSP.callbackUrl" hint="Assertion Consumer Service, or callback URL using the HTTP POST binding." placeholder="https://sp.example.com/callback"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Logout URL (optional)" v-model="newSP.logoutUrl" hint="The URL we will redirect IDP-initiated logout requests to." placeholder="https://sp.example.com/logout"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Logout callback URL (optional)" v-model="newSP.logoutCallbackUrl" hint="The URL we will redirect users to after an SP-initiated logout." placeholder="https://sp.example.com/logout/callback"/>
</v-flex>
</v-layout>
</v-container>
<v-text-field class="mb-2" label="Human-readable name" required autofocus v-model="newSP.name" hint="To help you identify this app." placeholder="My Service"/>
<v-text-field class="mb-2" label="Service URL" v-model="newSP.serviceUrl" hint="The URL used to access your service. For example, for a webapp, you can just use your website URL." placeholder="https://sp.example.com"/>
<h3 class="mb-2">SAML2 settings (optional)</h3>
<v-text-field class="mb-2" label="EntityID" v-model="newSP.entityId" hint="This is a URL to uniquely identify your service. It is sometimes the same as the metadata URL." placeholder="https://sp.example.com/metdadata"/>
<v-text-field class="mb-2" label="ACS URL" v-model="newSP.callbackUrl" hint="Assertion Consumer Service, or callback URL using the HTTP POST binding." placeholder="https://sp.example.com/callback"/>
<v-text-field class="mb-2" label="Logout URL" v-model="newSP.logoutUrl" hint="The URL we will redirect IDP-initiated logout requests to." placeholder="https://sp.example.com/logout"/>
<v-text-field label="Logout callback URL" v-model="newSP.logoutCallbackUrl" hint="The URL we will redirect users to after an SP-initiated logout." placeholder="https://sp.example.com/logout/callback"/>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat @click="dialog = false">Cancel</v-btn>
<v-btn color="blue darken-1" flat @click="create">{{editing ? 'Save': 'Create'}}</v-btn>
<v-btn @click="dialog = false">Cancel</v-btn>
<v-btn color="primary" @click="create">{{editing ? 'Save': 'Create'}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>

View File

@ -1,134 +1,148 @@
<template>
<div>
<h2>User accounts</h2>
<p>Manage the user accounts in this IdP. These users can authenticate against this IdP as part of a single sign-on flow.</p>
<p>Note that you will not be able to authenticate against this IdP using your SSO Tools account. You can safely use dummy names/emails here (e.g. "name@example.com"). Auto-generated users are given the default password <code>password</code>.</p>
<v-card>
<v-card-title primary-title><h3>User accounts</h3></v-card-title>
<v-card-text>
<p class="mb-2">These users can authenticate against this IdP as part of a single sign-on flow.</p>
<v-alert icon='mdi-information'>
<p><small>Note that you will not be able to authenticate against this IdP using your own SSO Tools account. You can safely use dummy names/emails here (e.g. "name@example.com"). Auto-generated users are given the default password <code>password</code>.</small></p>
</v-alert>
<v-btn color='primary' style="float:right;" v-on:click="openDialog"><v-icon>add</v-icon> Register a new user</v-btn>
<div style="clear:both;margin-bottom: 20px;" />
<div class="d-flex justify-end mt-5 mb-b">
<v-btn color='primary' v-on:click="openDialog" prepend-icon="mdi-plus">New user</v-btn>
</div>
<v-data-table :loading="loadingUsers" hide-actions class="elevation-1" :headers="tableHeaders" :items="users">
<template v-slot:items="props">
<td>{{props.item.firstName}}</td>
<td>{{props.item.lastName}}</td>
<td>{{props.item.email}}</td>
<td>
<v-menu offset-y transition="slide-y-transition">
<template v-slot:activator="{ on }">
<v-icon small class="mr-2" v-on="on">settings</v-icon>
</template>
<v-list>
<v-list-tile v-on:click="e => editUser(props.item)">
<v-list-tile-title><v-icon>edit</v-icon> Update</v-list-tile-title>
</v-list-tile>
<v-list-tile v-on:click="e => deleteUser(props.item._id)">
<v-list-tile-title><v-icon>delete</v-icon> Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</td>
</template>
</v-data-table>
<v-table :loading="loadingUsers" v-if="users.length">
<thead>
<tr>
<th>Name</th>
<th>Email</th>
<th class="text-right"/>
</tr>
</thead>
<tbody>
<tr v-for="user in users" key="user._id">
<td>{{user.firstName}} {{user.lastName}}</td>
<td>{{user.email}}</td>
<td>
<v-btn flat>
Manage
<v-menu activator="parent">
<v-list>
<v-list-item v-on:click="e => editUser(user)" prepend-icon="mdi-pencil">
<v-list-item-content>Update</v-list-item-content>
</v-list-item>
<v-list-item v-on:click="e => deleteUser(user._id)" prepend-icon="mdi-delete">
<v-list-item-content>Delete</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</td>
</tr>
</tbody>
</v-table>
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline">{{editing ? 'Edit user' : 'Create a new user'}}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="First name" required autofocus v-model="newUser.firstName"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Last name" v-model="newUser.lastName" />
</v-flex>
<v-flex xs12 sm6>
<v-text-field type="email" label="Email address" v-model="newUser.email" />
</v-flex>
<v-flex xs12 sm6>
<v-dialog v-model="dialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline">{{editing ? 'Edit user' : 'Create a new user'}}</span>
</v-card-title>
<v-card-text>
<div class="d-flex">
<v-text-field class="mr-2" label="First name" required autofocus v-model="newUser.firstName"/>
<v-text-field class="mr-2" label="Last name" v-model="newUser.lastName" />
</div>
<div class="d-flex">
<v-text-field class="mr-2" type="email" label="Email address" v-model="newUser.email" />
<v-text-field type="password" label="Password" hint="This can be changed later." v-model="newUser.password" />
</v-flex>
</v-layout>
</div>
<div v-if="attributes.length">
<h3>Extra attributes</h3>
<p>Specifying a value for an attribute below will include that value in assertions made during the SSO process, overriding the attribute's default value. Leave values here blank to send the default attribute value instead.</p>
<div v-if="attributes.length" class="mt-10">
<h3>Additional user attributes</h3>
<p>Specifying a value for an attribute below will include that value in assertions made during the SSO process, overriding the attribute's default value. Leave a value blank to send the default attribute value instead.</p>
<v-layout>
<v-flex xs12 sm6 v-for="attribute in attributes">
<v-text-field :label="attribute.name" :hint="`Default value: ${attribute.defaultValue ? `'${attribute.defaultValue}'` : 'none'}`" v-model="newUser.attributes[attribute._id]" />
</v-flex>
</v-layout>
</div>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat @click="dialog = false">Cancel</v-btn>
<v-btn color="blue darken-1" dark @click="create">{{editing ? 'Save' : 'Create'}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<div class="d-flex flex-wrap mt-5">
<div class="mr-2 w-25" v-for="attribute in attributes">
<v-text-field :label="attribute.name" :hint="`Default value: ${attribute.defaultValue ? `'${attribute.defaultValue}'` : 'none'}`" v-model="newUser.attributes[attribute._id]" />
</div>
</div>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="dialog = false">Cancel</v-btn>
<v-btn color="primary" dark @click="create">{{editing ? 'Save' : 'Create'}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-text>
</v-card>
<h2 style="margin-top:30px;">Extra attributes</h2>
<p>Manage custom attributes that can be passed to the Service Provider during sign-on.</p>
<v-btn color='primary' style="float:right;" v-on:click="openAttributeDialog"><v-icon>add</v-icon> New attribute</v-btn>
<div style="clear:both;margin-bottom: 20px;" />
<v-card class="mt-10">
<v-card-title primary-title><h3>Additional user attributes</h3></v-card-title>
<v-card-text>
<p class="mb-2">Manage custom attributes that can be passed to the Service Provider during sign-on. These attributes are in addition to the core user account attributes (such as first name, last name, and email address).</p>
<v-data-table :loading="loadingAttributes" hide-actions class="elevation-1" :headers="attributeTableHeaders" :items="attributes">
<template v-slot:items="props">
<td>{{props.item.name}}</td>
<td>{{props.item.defaultValue}}</td>
<td>{{props.item.samlMapping}}</td>
<td>
<v-menu offset-y transition="slide-y-transition">
<template v-slot:activator="{ on }">
<v-icon small class="mr-2" v-on="on">settings</v-icon>
</template>
<v-list>
<v-list-tile v-on:click="e => editAttribute(props.item)">
<v-list-tile-title><v-icon>edit</v-icon> Update</v-list-tile-title>
</v-list-tile>
<v-list-tile v-on:click="e => deleteAttribute(props.item._id)">
<v-list-tile-title><v-icon>delete</v-icon> Delete</v-list-tile-title>
</v-list-tile>
</v-list>
</v-menu>
</td>
</template>
</v-data-table>
<div class="d-flex justify-end mt-5 mb-b">
<v-btn color='primary' v-on:click="openAttributeDialog" prepend-icon="mdi-plus">New attribute</v-btn>
</div>
<v-dialog v-model="attributeDialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline">{{editingAttribute ? 'Edit attribute' : 'Create a new custom attribute'}}</span>
</v-card-title>
<v-card-text>
<v-container grid-list-md>
<v-layout wrap>
<v-flex xs12 sm6>
<v-text-field label="Name" required autofocus v-model="newAttribute.name" hint="We'll use this as the attribute name in the assertion unless a mapping is provided." />
</v-flex>
<v-flex xs12 sm6>
<v-table :loading="loadingAttributes">
<thead>
<tr>
<th>Human name</th>
<th>Attribute key</th>
<th>Default value</th>
<th />
</tr>
</thead>
<tbody>
<tr v-for="attribute in attributes" key="attribute._id">
<td>{{attribute.name}}</td>
<td>{{attribute.samlMapping}}</td>
<td>{{attribute.defaultValue}}</td>
<td>
<v-btn flat>
Manage
<v-menu activator="parent">
<v-list>
<v-list-item v-on:click="e => editAttribute(attribute)" prepend-icon="mdi-pencil">
<v-list-item-content>Update</v-list-item-content>
</v-list-item>
<v-list-item v-on:click="e => deleteAttribute(attribute._id)" prepend-icon="mdi-delete">
<v-list-item-content>Delete</v-list-item-content>
</v-list-item>
</v-list>
</v-menu>
</v-btn>
</td>
</tr>
</tbody>
</v-table>
<v-dialog v-model="attributeDialog" persistent max-width="600px">
<v-card>
<v-card-title>
<span class="headline">{{editingAttribute ? 'Edit attribute' : 'Create a new custom attribute'}}</span>
</v-card-title>
<v-card-text>
<div class="dd-flex">
<v-text-field class="mb-2" label="Human-readable name" required autofocus v-model="newAttribute.name" hint="We'll use this as the attribute key unless a mapping is provided." />
<v-text-field class="mb-2" label="Attribute key" v-model="newAttribute.samlMapping" hint="Values for this attribute will be sent with this key during the SSO process." />
<v-text-field label="Default value" v-model="newAttribute.defaultValue" hint="If not overridden in the user itself, this value will be sent as a default."/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="SAML2 mapping (optional)" v-model="newAttribute.samlMapping" hint="If provided, we'll label the attribute value with this name in SAML2 assertions." />
</v-flex>
</v-layout>
</v-container>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="blue darken-1" flat @click="attributeDialog = false">Cancel</v-btn>
<v-btn color="blue darken-1" dark @click="createAttribute">{{editingAttribute ? 'Save' : 'Create'}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn @click="attributeDialog = false">Cancel</v-btn>
<v-btn color="primary" @click="createAttribute">{{editingAttribute ? 'Save' : 'Create'}}</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-text>
</v-card>
</div>
</template>

View File

@ -1,9 +1,7 @@
<template>
<div>
<v-container>
<v-layout style="margin-top: 50px;" :justify-center="true">
<v-flex xs12 md8>
<div v-if="page === 0">
<div style="text-align:center; margin-bottom: 30px;">
@ -25,7 +23,7 @@
<p>You're currently in the SSO Tools sandbox, which means that if you end your browser session you'll lose access to your IdP. If you want to come back to continue working on this IdP at a later date, we recommend creating an account to secure it and to save your progress.</p>
</v-alert>
<v-alert type="error" :value="error">
<v-alert type="error" v-if="error">
<h3>Could not create IdP</h3>
<p>{{error}}</p>
</v-alert>
@ -41,75 +39,49 @@
<p>Register a user with {{name}}. This user will be able to login via your IdP and access your connected apps.</p>
<p>You can always add more users later.</p>
<p><strong>Note that you cannot use your normal SSO Tools account to authenticate against your IdP.</strong> You can safely use dummy information for your users, since we won't be emailing them!</p>
<v-layout wrap style="margin: 30px auto;">
<v-flex xs12 sm6>
<v-text-field autofocus v-model="newUser.firstName" label="First name" required placeholder="Jane"></v-text-field>
</v-flex>
<v-flex xs12 sm6>
<v-text-field v-model="newUser.lastName" label="Last name" required placeholder="Doe"></v-text-field>
</v-flex>
<v-flex xs12 sm6>
<v-text-field v-model="newUser.email" label="Email address" required placeholder="jane@example.com"></v-text-field>
</v-flex>
<v-flex xs12 sm6>
<v-text-field v-model="newUser.password" label="Account password" required type='password' placeholder="password"></v-text-field>
</v-flex>
</v-layout>
<v-btn @click="skip">Skip this step</v-btn>
<p class="mt-10"><strong>Note that you cannot use your normal SSO Tools account to authenticate against your IdP.</strong> You can safely use dummy information for your users, since we won't be emailing them!</p>
<div class="d-block d-sm-flex flex-wrap mt-10 mb-10">
<v-text-field class="mr-2" autofocus v-model="newUser.firstName" label="First name" required placeholder="Jane"></v-text-field>
<v-text-field class="mr-2" v-model="newUser.lastName" label="Last name" required placeholder="Doe"></v-text-field>
<v-text-field class="mr-2" v-model="newUser.email" label="Email address" required placeholder="jane@example.com"></v-text-field>
<v-text-field class="mr-2" v-model="newUser.password" label="Account password" required type='password' placeholder="password"></v-text-field>
</div>
<v-btn @click="skip" class="mr-2">Skip this step</v-btn>
<v-btn :loading="creating" color='primary' v-on:click="createUser">Next</v-btn>
</div>
<div v-if="page === 2" style="text-align:center;">
<h4>Step 3</h4>
<h1>Connect an app</h1>
<p>If you know the SAML2 settings for a service provider, you can them below to connect it to this IdP.</p>
<p>You can find this IdP's own SAML settings after this setup. You can always add more apps later too.</p>
<v-layout wrap style="margin: 30px auto;">
<v-flex xs12 sm6>
<v-text-field autofocus label="Friendly name" required autofocus v-model="newSP.name" hint="To help you identify this SP." placeholder="My Service"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Service URL" v-model="newSP.serviceUrl" hint="The URL used to access your service. For example, for a webapp, you can just use your website URL." placeholder="https://sp.example.com"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="EntityID" v-model="newSP.entityId" hint="This is a URL to uniquely identify your service. It is sometimes the same as the metadata URL." placeholder="https://sp.example.com/metdadata"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="ACS URL" v-model="newSP.callbackUrl" hint="Assertion Consumer Service, or callback URL using the HTTP POST binding." placeholder="https://sp.example.com/callback"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Logout URL (optional)" v-model="newSP.logoutUrl" hint="The URL we will redirect IdP-initiated logout requests to." placeholder="https://sp.example.com/logout"/>
</v-flex>
<v-flex xs12 sm6>
<v-text-field label="Logout callback URL (optional)" v-model="newSP.logoutCallbackUrl" hint="The URL we will redirect users to after an SP-initiated logout." placeholder="https://sp.example.com/logout/callback"/>
</v-flex>
</v-layout>
<v-btn @click="skip">Skip this step</v-btn>
<h1>Create an app</h1>
<p>If you already have an app you'd like to connect to SSO Tools, you can add it below.</p>
<p>You can find and modify its SSO settings later.</p>
<div class="d-block d-sm-flex flex-wrap mt-10 mb-10">
<v-text-field class="mr-2" label="Friendly name" required autofocus v-model="newSP.name" hint="To help you identify this SP." placeholder="My Service"/>
<v-text-field label="Service URL" v-model="newSP.serviceUrl" hint="Your app's URL" placeholder="https://myapp.com"/>
</div>
<v-btn @click="skip" class="mr-2">Skip this step</v-btn>
<v-btn :loading="creating" color='primary' v-on:click="createSP">Next</v-btn>
</div>
<div v-if="page === 3" style="text-align:center;">
<div v-if="page === 3" class="w-100 mt-15 ml-auto mr-auto text-center">
<h1>All done!</h1>
<p>Your new IdP is ready.</p>
<h3 style="margin-top: 50px;">What would you like to do now?</h3>
<h3 class="ma-10">What would you like to do now?</h3>
<div style="margin-bottom:20px;"> <v-btn color='primary' :to="`/idps/${this._id}`">View IdP</v-btn></div>
<div><v-btn fluid :to="`/idps/${this._id}/saml`">View SAML2 setup</v-btn></div>
<div><v-btn :to="`/idps/${this._id}/users`">Add more users</v-btn></div>
<div><v-btn :to="`/idps/${this._id}/sps`">Manage apps</v-btn></div>
<div class="mb-5"> <v-btn color='primary' :to="`/idps/${this._id}`">View IdP</v-btn></div>
<div class="d-block d-sm-flex flex-wrap mt-10 justify-center">
<v-btn class="ma-2" fluid :to="`/idps/${this._id}/saml`">View SAML2 setup</v-btn>
<v-btn class="ma-2" :to="`/idps/${this._id}/users`">Add more users</v-btn>
<v-btn class="ma-2" :to="`/idps/${this._id}/sps`">Manage apps</v-btn>
</div>
</div>
</v-flex>
</v-layout>
</v-container>
</div>
</template>
@ -169,7 +141,7 @@ export default {
this.creating = false;
this.page = this.page + 1;
}, err => {
this.creating = false;
this.creating = false;
});
}
},
@ -181,7 +153,7 @@ export default {
this.creating = false;
this.page = this.page + 1;
}, err => {
this.creating = false;
this.creating = false;
});
},
},

View File

@ -1,10 +1,14 @@
import Vue from 'vue'
import Vuetify from 'vuetify'
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import { createStore } from 'vuex'
import 'vuetify/styles'
import { createVuetify } from 'vuetify'
import * as components from 'vuetify/components'
import * as directives from 'vuetify/directives'
import { aliases, mdi } from 'vuetify/iconsets/mdi'
import '@mdi/font/css/materialdesignicons.css'
import App from './App.vue'
import 'vuetify/dist/vuetify.min.css'
import Home from './components/Home.vue';
import ResetPassword from './components/ResetPassword.vue';
@ -22,11 +26,7 @@ import IDPLogs from './components/IdpLogs.vue';
import PrivacyPolicy from './components/legal/PrivacyPolicy.vue';
import TermsOfUse from './components/legal/TermsOfUse.vue';
Vue.use(Vuetify);
Vue.use(VueRouter);
Vue.use(Vuex);
const store = new Vuex.Store({
const store = createStore({
state: {
loggedIn: false,
user: null,
@ -59,13 +59,13 @@ const store = new Vuex.Store({
state.loginOpen = open;
},
}
});
})
const router = new VueRouter({
const router = createRouter({
scrollBehavior() {
return { x: 0, y: 0 };
return { left: 0, top: 0 };
},
mode: 'history',
history: createWebHistory(),
routes: [
{ path: '/', component: Home },
{ path: '/privacy', component: PrivacyPolicy },
@ -75,7 +75,7 @@ const router = new VueRouter({
{ path: '/dashboard', component: Dashboard },
{ path: '/idps/new', component: NewIDP },
{ path: '/idps/:id', component: IDP, children: [
{ path: '/', component: IDPHome },
{ path: '', component: IDPHome },
{ path: 'users', component: IDPUsers },
{ path: 'settings', component: IDPSettings },
{ path: 'sps', component: IDPSPs },
@ -85,4 +85,20 @@ const router = new VueRouter({
]
})
new Vue({ store, router, render: h => h(App) }).$mount('#app')
const vuetify = createVuetify({
components,
directives,
icons: {
defaultSet: 'mdi',
aliases,
sets: {
mdi,
}
},
})
const app = createApp(App)
app.use(store)
app.use(vuetify)
app.use(router)
app.mount('#app')

5
web/vite.config.js Normal file
View File

@ -0,0 +1,5 @@
import vue from '@vitejs/plugin-vue'
export default {
plugins: [vue()]
}

File diff suppressed because it is too large Load Diff