Rename and fixes

This commit is contained in:
Karolis Kundrotas
2021-09-10 11:59:34 +03:00
parent 257308e096
commit c3bb8983ef
29 changed files with 11 additions and 27 deletions

23
KTUSAPS/ClientApp/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@@ -0,0 +1,6 @@
module.exports = {
trailingComma: 'es5',
tabWidth: 2,
semi: false,
singleQuote: true,
}

View File

@@ -0,0 +1,24 @@
# clientapp
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).

View File

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

12269
KTUSAPS/ClientApp/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
{
"name": "clientapp",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@popperjs/core": "^2.10.1",
"axios": "^0.20.0-0",
"bootstrap": "^5.1.1",
"cookies-js": "^1.2.3",
"core-js": "^3.7.0",
"jwt-decode": "^3.1.2",
"vue": "^3.0.2",
"vue-loader-v16": "npm:vue-loader@^16.0.0-alpha.3",
"vue-router": "^4.0.0-rc.5",
"vuex": "^4.0.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^4.5.9",
"@vue/cli-plugin-eslint": "^4.5.9",
"@vue/cli-service": "^4.5.9",
"@vue/compiler-sfc": "^3.0.2",
"babel-eslint": "^10.1.0",
"eslint": "^6.8.0",
"eslint-plugin-vue": "^7.1.0",
"sass": "^1.33.0",
"sass-loader": "^10.2.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<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 %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

View File

@@ -0,0 +1,49 @@
<template>
<nav-menu></nav-menu>
<div v-if="isLocal" class="container">
<div class="alert alert-danger">
<h4 class="alert-heading">Lokali sistemą aptikta</h4>
<span>
Buvo aptikta, kad aplikacija veikia ant lokalios mašinos. Tai didelis
šansas kad sistemoje pateikiama informacija nėra saugi.
</span>
</div>
</div>
<div v-if="isInsecure" class="container">
<div class="alert alert-danger">
<h4 class="alert-heading">Nesaugus ryšys</h4>
<span>
Buvo aptikta, kad yra užmegztas nesaugus ryšys. Tai reiškią kad betkokie
duomenys perduodami naudojant sistemą, įskaitant ir prisijungimo tokeną,
yra neapsaugoti.
</span>
</div>
</div>
<router-view />
</template>
<script>
import NavMenu from './components/NavMenu.vue'
export default {
name: 'App',
components: {
NavMenu,
},
created() {
this.$store.dispatch('auth/initialize')
},
computed: {
isLocal() {
return (
location.hostname === 'localhost' || location.hostname.startsWith('127')
)
},
isInsecure() {
return location.protocol !== 'https:'
},
},
}
</script>
<style></style>

View File

@@ -0,0 +1 @@


Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

View File

@@ -0,0 +1,2 @@
@import "custom";
@import "~bootstrap/scss/bootstrap";

View File

@@ -0,0 +1,65 @@
<template>
<header class="mb-3">
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container-fluid">
<router-link :to="{ name: 'Home' }" class="navbar-brand"
>KTU SA Problemų sprendimo sistema</router-link
>
<button
class="navbar-toggler"
type="button"
data-bs-toggle="collapse"
data-bs-target="#navbarNav"
aria-controls="navbarNav"
aria-expanded="false"
aria-label="Toggle navigation"
>
<span class="navbar-toggler-icon"></span>
</button>
<div
class="collapse navbar-collapse"
:class="{ show: isExpanded }"
id="navbarNav"
>
<ul class="navbar-nav me-auto">
<li class="nav-item">
<router-link :to="{ name: 'Home' }" class="nav-link"
>Pagrindinis</router-link
>
</li>
</ul>
<div class="navbar-nav">
<span v-if="$store.getters['auth/isValid']" class="navbar-text"
>Prisijungta kaip {{ $store.getters['auth/email'] }}</span
>
<div v-else class="nav-item">
<a :href="$store.getters['auth/loginUrl']" class="nav-link"
>Prisijungti</a
>
</div>
</div>
</div>
</div>
</nav>
</header>
</template>
<script>
export default {
name: 'NavMenu',
data() {
return {
isExpanded: false,
}
},
methods: {
collapse() {
this.isExpanded = false
},
toggle() {
this.isExpanded = !this.isExpanded
},
},
}
</script>

View File

@@ -0,0 +1,15 @@
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import './assets/main.scss'
import 'bootstrap'
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')
window.r = router

View File

@@ -0,0 +1,107 @@
<template>
<div class="container">
<h1>KTU SA Problemų sprendimo sistema</h1>
<template v-if="$store.getters['auth/isValid']">
<div class="alert alert-success">
<h4 class="alert-heading">Tu esi prisijungęs</h4>
<span>
Kliento aplikacija turi tavo saugos raktą. Aplikacija žino, kad tavo
el. paštas yra: <b>{{ $store.getters['auth/email'] }}</b>
</span>
</div>
<h2>Visi laukai gaunami Azure Active Directory</h2>
<table class="table">
<thead>
<tr>
<th scope="col">Pavadinimas</th>
<th scope="col">Reikšmė</th>
</tr>
</thead>
<tbody>
<tr v-for="(value, key) in authDataTable" :key="key">
<td>{{ key }}</td>
<td>
<pre>{{ value }}</pre>
</td>
</tr>
</tbody>
</table>
<h3>Techninė duomenų reprezentacija</h3>
<a
href="https://docs.microsoft.com/en-us/azure/active-directory/develop/access-tokens"
>Dokumentacija apie laukų reikšmes</a
>
<pre>{{ $store.state.auth.tokenData }}</pre>
<h3>Saugos raktas.</h3>
<pre>{{ $store.state.auth.token }}</pre>
<h3>Serverio tokeno patikrinimas</h3>
<button type="button" class="btn btn-primary" @click="serverVerify">
Patikrinti
</button>
<h5>Verifikacijos atsakas:</h5>
<pre>
{{ verificationResult }}
</pre>
</template>
<div v-else class="alert alert-danger" role="alert">
<h4 class="alert-heading">Tu neprisijungęs</h4>
<p>Prašom paspausti prisijungimo mygtuką navigacijos juostoje.</p>
</div>
</div>
</template>
<script>
import axios from 'axios'
const names = {
aud: 'AppId (Audience)',
iss: 'Išdavėjas',
iat: 'Išdavimo momentas',
nbf: 'Negalioja anksčiau nei',
exp: 'Galiojimo pabaiga',
email: 'El. paštas',
nonce: 'Aplikacijos sugeneruota nepasikartojanti reikšmė',
sub: 'Subjektas (Vartotojo Id)',
tid: 'Tenanto Identifikatorius',
ver: 'OAuth versija',
}
function lookupName(key) {
if (names[key]) return names[key]
return key
}
export default {
data() {
return {
verificationResult: null,
}
},
computed: {
authDataTable() {
return Object.fromEntries(
Object.entries(this.$store.state.auth.tokenData).map(([key, value]) => [
lookupName(key),
value,
])
)
},
},
methods: {
serverVerify() {
this.verificationResult = null
axios
.get('/test/authed', {
headers: { Authorization: `Bearer ${this.$store.state.auth.token}` },
})
.then(response => {
this.verificationResult = response.data
})
.catch(function(error) {
alert(error)
})
},
},
}
</script>

View File

@@ -0,0 +1,25 @@
<template>
<div class="container">
<h1>Palaukite kol nustatysime jūsų tapatybe...</h1>
</div>
</template>
<script>
const tokenRegex = /id_token=(.*\..*\..*)&/
export default {
name: 'OIDC',
created() {
if (this.openIdToken)
this.$store.dispatch('auth/setToken', this.openIdToken)
this.$router.push({ name: 'Home' })
},
computed: {
openIdToken() {
const matches = this.$route.hash.match(tokenRegex)
if (!matches) return null
return matches[1]
},
},
}
</script>

View File

@@ -0,0 +1,23 @@
import { createWebHistory, createRouter } from 'vue-router'
import Home from '@/pages/Home.vue'
import OidcEndpoint from '@/pages/OidcEndpoint.vue'
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/oidc',
name: 'OpenID connect endpoint',
component: OidcEndpoint,
},
]
const router = createRouter({
history: createWebHistory(),
routes,
})
export default router

View File

@@ -0,0 +1,12 @@
import { createStore, createLogger } from "vuex";
import auth from "./modules/auth";
const debug = process.env.NODE_ENV !== "production";
export default createStore({
modules: {
auth,
},
strict: debug,
plugins: debug ? [createLogger()] : [],
});

View File

@@ -0,0 +1,145 @@
import Cookies from 'cookies-js'
import jwt_decode from 'jwt-decode'
import axios from 'axios'
const TokenCookieName = 'ktusaktutoken'
const ClientIdCookieName = 'ktusakacas'
const AuthorityCookieName = 'ktusakeksas'
const TenantCookieName = 'ktusalaimis'
const NonceCookieName = 'ktusakumpikas'
const Scope = 'openid email'
// initial state
const state = () => ({
token: null,
tokenData: null,
clientId: null, // 5931fda0-e9e0-4754-80c2-18bcb9d9561a
authority: null, // https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0
tenant: null, // 3415f2f7-f5a8-4092-b52a-003aaf844853
})
const callbackUrl =
window.location.protocol + '//' + window.location.host + '/oidc'
// getters
const getters = {
isReady(state) {
if (
state.clientId == null ||
state.authority == null ||
state.tenant == null
)
return false
return true
},
isValid(state, getters) {
if (!getters.isReady) return false
if (state.token == null || state.tokenData == null) return false
const d = state.tokenData
if (d.nonce !== state.nonce) return false
if (d.iss !== state.authority) return false
if (d.aud !== state.clientId) return false
const now = new Date()
const exp = new Date(d.exp * 1000)
if (now > exp) return false
return true
},
email(state, getters) {
if (!getters.isValid) return null
return state.tokenData.email
},
userId(state, getters) {
if (!getters.isValid) return null
return state.tokenData.email
},
loginUrl(state, getters) {
if (!getters.isReady) return null
return `https://login.microsoftonline.com/${
state.tenant
}/oauth2/v2.0/authorize?client_id=${
state.clientId
}&redirect_uri=${encodeURIComponent(
callbackUrl
)}&response_type=id_token&scope=${Scope}&nonce=${state.nonce}`
},
}
// actions
const actions = {
async initialize({ commit }) {
const token = Cookies.get(TokenCookieName)
const primaryClientId = Cookies.get(ClientIdCookieName)
const primaryAuthority = Cookies.get(AuthorityCookieName)
const primaryTenant = Cookies.get(TenantCookieName)
const nonce = Cookies.get(NonceCookieName)
if (!nonce) {
const newNonce =
Date.now().toString(36) +
Math.random()
.toString(36)
.substring(2)
Cookies.set(NonceCookieName, newNonce)
commit('setNonce', newNonce)
} else {
commit('setNonce', nonce)
}
commit('setToken', token)
commit('computeTokenVars')
commit('setMetadata', [primaryClientId, primaryAuthority, primaryTenant])
axios
.get('/api/AuthMetadata')
.then(response => {
Cookies.set(ClientIdCookieName, response.data.clientId)
Cookies.set(AuthorityCookieName, response.data.authority)
Cookies.set(TenantCookieName, response.data.tenant)
commit('setMetadata', [
response.data.clientId,
response.data.authority,
response.data.tenant,
])
})
.catch(error => {
console.error(error)
})
},
async setToken({ commit }, token) {
Cookies.set(TokenCookieName, token)
commit('setToken', token)
commit('computeTokenVars')
},
}
// mutations
const mutations = {
setToken(state, token) {
state.token = token
},
setNonce(state, nonce) {
state.nonce = nonce
},
computeTokenVars(state) {
if (state.token == null) return
try {
state.tokenData = jwt_decode(state.token)
} catch {
console.log('Token was invalid.')
state.tokenData = null
state.token = null
}
},
setMetadata(state, [clientId, authority, tenant]) {
state.clientId = clientId
state.authority = authority
state.tenant = tenant
},
}
export default {
namespaced: true,
state,
getters,
actions,
mutations,
}

View File

@@ -0,0 +1,8 @@
module.exports = {
runtimeCompiler: true,
chainWebpack: (config) => {
config.resolve.alias
.set("balm-ui-plus", "balm-ui/dist/balm-ui-plus.js")
.set("balm-ui-css", "balm-ui/dist/balm-ui.css");
},
};

View File

@@ -0,0 +1,24 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace KTUSAPS.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthMetadataController : ControllerBase
{
private readonly IConfiguration _configuration;
public AuthMetadataController(IConfiguration configuration)
{
_configuration = configuration;
}
[HttpGet]
public object Index() => new { ClientId = _configuration["ClientId"], Authority = _configuration["Authority"], Tenant = _configuration["Tenant"] };
}
}

View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace KTUSAPS.Controllers
{
[Route("[controller]")]
[Authorize]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
public object[] Index()
{
return HttpContext.User.Claims.Select(x => new { Name = x.Type, Value= x.Value }).ToArray();
}
[HttpGet("authed")]
public bool IsAuthed() => true;
}
}

52
KTUSAPS/KTUSAPS.csproj Normal file
View File

@@ -0,0 +1,52 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<RootNamespace>KTUSAPS</RootNamespace>
</PropertyGroup>
<PropertyGroup>
<SpaRoot>ClientApp\</SpaRoot>
<DefaultItemExcludes>$(DefaultItemExcludes);$(SpaRoot)node_modules\**</DefaultItemExcludes>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="5.0.8" />
<PackageReference Include="VueCliMiddleware" Version="3.0.0" />
</ItemGroup>
<ItemGroup>
<!-- Don't publish the SPA source files, but do show them in the project files list -->
<Content Remove="$(SpaRoot)**" />
<None Remove="$(SpaRoot)**" />
<None Include="$(SpaRoot)**" Exclude="$(SpaRoot)node_modules\**" />
</ItemGroup>
<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">
<!-- Ensure Node.js is installed -->
<Exec Command="node --version" ContinueOnError="true">
<Output TaskParameter="ExitCode" PropertyName="ErrorCode" />
</Exec>
<Error Condition="'$(ErrorCode)' != '0'" Text="Node.js is required to build and run this project. To continue, please install Node.js from https://nodejs.org/, and then restart your command prompt or IDE." />
<Message Importance="high" Text="Restoring dependencies using 'npm'. This may take several minutes..." />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>
<Target Name="PublishRunWebpack" AfterTargets="ComputeFilesToPublish">
<!-- As part of publishing, ensure the JS resources are freshly built in production mode -->
<Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
<Exec WorkingDirectory="$(SpaRoot)" Command="npm run build -- --prod" />
<!-- Include the newly-built files in the publish output -->
<ItemGroup>
<DistFiles Include="$(SpaRoot)dist\**" />
<ResolvedFileToPublish Include="@(DistFiles->'%(FullPath)')" Exclude="@(ResolvedFileToPublish)">
<RelativePath>%(DistFiles.Identity)</RelativePath>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</ResolvedFileToPublish>
</ItemGroup>
</Target>
</Project>

26
KTUSAPS/Program.cs Normal file
View File

@@ -0,0 +1,26 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace KTUSAPS
{
public class Program
{
public static void Main(string[] args)
{
CreateHostBuilder(args).Build().Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}

View File

@@ -0,0 +1,29 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:50598",
"sslPort": 0
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"KTUSAPS": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5000"
}
}
}

75
KTUSAPS/Startup.cs Normal file
View File

@@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using VueCliMiddleware;
namespace KTUSAPS
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/dist";
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Audience = "5931fda0-e9e0-4754-80c2-18bcb9d9561a";
options.Authority = "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0";
});
services.AddAuthorization((configure) =>
{
configure.DefaultPolicy = new Microsoft.AspNetCore.Authorization.AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
});
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseSpaStaticFiles();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp/";
if (env.IsDevelopment())
{
spa.UseVueCli(npmScript: "serve");
}
});
}
}
}

View File

@@ -0,0 +1,12 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
}

13
KTUSAPS/appsettings.json Normal file
View File

@@ -0,0 +1,13 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ClientId": "5931fda0-e9e0-4754-80c2-18bcb9d9561a",
"Authority": "https://login.microsoftonline.com/3415f2f7-f5a8-4092-b52a-003aaf844853/v2.0",
"Tenant": "3415f2f7-f5a8-4092-b52a-003aaf844853"
}