get full name information, make sure its displayed in the UI.
added a white on transparent banner image.
This commit is contained in:
parent
c0e996dc74
commit
bcb3f58d6e
|
@ -79,12 +79,13 @@ cd frontend
|
||||||
npm run dist -- -c sandbox
|
npm run dist -- -c sandbox
|
||||||
|
|
||||||
# In terminal #2, run the following
|
# In terminal #2, run the following
|
||||||
|
docker build -t fasten-couchdb -f docker/couchdb/Dockerfile .
|
||||||
|
docker run --rm -it -p 5984:5984 -v `pwd`/.couchdb/data:/opt/couchdb/data -v `pwd`/.couchdb/config:/opt/couchdb/etc/local.d fasten-couchdb
|
||||||
|
|
||||||
|
# In terminal #3, run the following
|
||||||
go mod vendor
|
go mod vendor
|
||||||
go run backend/cmd/fasten/fasten.go start --config ./config.dev.yaml --debug
|
go run backend/cmd/fasten/fasten.go start --config ./config.dev.yaml --debug
|
||||||
|
|
||||||
# In terminal #3, run the following
|
|
||||||
docker build -t fasten-couchdb -f docker/couchdb/Dockerfile .
|
|
||||||
docker run --rm -it -p 5984:5984 -v `pwd`/.couchdb/data:/opt/couchdb/data -v `pwd`/.couchdb/config:/opt/couchdb/etc/local.d fasten-couchdb
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Now you can open a browser to `http://localhost:9090` to see the Fasten UI.
|
Now you can open a browser to `http://localhost:9090` to see the Fasten UI.
|
||||||
|
|
|
@ -57,6 +57,8 @@ type couchDbUser struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
|
|
||||||
|
FullName string `json:"full_name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cr *couchdbRepository) CreateUser(ctx context.Context, user *models.User) error {
|
func (cr *couchdbRepository) CreateUser(ctx context.Context, user *models.User) error {
|
||||||
|
@ -67,6 +69,8 @@ func (cr *couchdbRepository) CreateUser(ctx context.Context, user *models.User)
|
||||||
Type: "user",
|
Type: "user",
|
||||||
Roles: []string{},
|
Roles: []string{},
|
||||||
Password: user.Password,
|
Password: user.Password,
|
||||||
|
|
||||||
|
FullName: user.FullName,
|
||||||
}
|
}
|
||||||
db := cr.client.DB(ctx, "_users")
|
db := cr.client.DB(ctx, "_users")
|
||||||
_, err := db.Put(ctx, newUser.ID, newUser)
|
_, err := db.Put(ctx, newUser.ID, newUser)
|
||||||
|
@ -97,6 +101,19 @@ func (cr *couchdbRepository) VerifyUser(ctx context.Context, user *models.User)
|
||||||
}
|
}
|
||||||
log.Printf("SESSION INFO: %v", session)
|
log.Printf("SESSION INFO: %v", session)
|
||||||
//TODO: return session info
|
//TODO: return session info
|
||||||
|
|
||||||
|
//lookup the user in the user database using admin creds
|
||||||
|
adminDb := cr.client.DB(ctx, "_users")
|
||||||
|
userRow := adminDb.Get(ctx, kivik.UserPrefix+session.Name)
|
||||||
|
userDoc := map[string]interface{}{}
|
||||||
|
err = userRow.ScanDoc(&userDoc)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if userFullName, hasUserFullName := userDoc["full_name"]; hasUserFullName {
|
||||||
|
user.FullName = userFullName.(string)
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package models
|
package models
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
FullName string `json:"full_name"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
|
|
||||||
func AuthSignup(c *gin.Context) {
|
func AuthSignup(c *gin.Context) {
|
||||||
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
|
||||||
|
appConfig := c.MustGet("CONFIG").(config.Interface)
|
||||||
|
|
||||||
var user models.User
|
var user models.User
|
||||||
if err := c.ShouldBindJSON(&user); err != nil {
|
if err := c.ShouldBindJSON(&user); err != nil {
|
||||||
|
@ -25,7 +26,10 @@ func AuthSignup(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true})
|
//TODO: we can derive the encryption key and the hash'ed user from the responseData sub. For now the Sub will be the user id prepended with hello.
|
||||||
|
userFastenToken, err := jwtGenerateFastenTokenFromUser(user, appConfig.GetString("jwt.issuer.key"))
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": userFastenToken})
|
||||||
}
|
}
|
||||||
|
|
||||||
func AuthSignin(c *gin.Context) {
|
func AuthSignin(c *gin.Context) {
|
||||||
|
@ -49,14 +53,22 @@ func AuthSignin(c *gin.Context) {
|
||||||
c.JSON(http.StatusOK, gin.H{"success": true, "data": userFastenToken})
|
c.JSON(http.StatusOK, gin.H{"success": true, "data": userFastenToken})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UserRegisteredClaims struct {
|
||||||
|
FullName string `json:"full_name"`
|
||||||
|
jwt.RegisteredClaims
|
||||||
|
}
|
||||||
|
|
||||||
func jwtGenerateFastenTokenFromUser(user models.User, issuerSigningKey string) (string, error) {
|
func jwtGenerateFastenTokenFromUser(user models.User, issuerSigningKey string) (string, error) {
|
||||||
log.Printf("ISSUER KEY: " + issuerSigningKey)
|
log.Printf("ISSUER KEY: " + issuerSigningKey)
|
||||||
userClaims := jwt.RegisteredClaims{
|
userClaims := UserRegisteredClaims{
|
||||||
// In JWT, the expiry time is expressed as unix milliseconds
|
FullName: user.FullName,
|
||||||
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
RegisteredClaims: jwt.RegisteredClaims{
|
||||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
// In JWT, the expiry time is expressed as unix milliseconds
|
||||||
Issuer: "docker-fastenhealth",
|
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
|
||||||
Subject: user.Username,
|
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||||
|
Issuer: "docker-fastenhealth",
|
||||||
|
Subject: user.Username,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//FASTEN_JWT_ISSUER_KEY
|
//FASTEN_JWT_ISSUER_KEY
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
<div class="az-img-user">
|
<div class="az-img-user">
|
||||||
<img src="assets/logo/logo-text.png" alt="">
|
<img src="assets/logo/logo-text.png" alt="">
|
||||||
</div><!-- az-img-user -->
|
</div><!-- az-img-user -->
|
||||||
<h6>{{current_user}}</h6>
|
<h6>{{current_user_claims.full_name || current_user_claims.email || current_user_claims.sub}}</h6>
|
||||||
<span>Adminstrator</span>
|
<span>Adminstrator</span>
|
||||||
</div><!-- az-header-profile -->
|
</div><!-- az-header-profile -->
|
||||||
|
|
||||||
|
|
|
@ -2,20 +2,21 @@ import { Component, OnInit } from '@angular/core';
|
||||||
import {FastenDbService} from '../../services/fasten-db.service';
|
import {FastenDbService} from '../../services/fasten-db.service';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import {AuthService} from '../../services/auth.service';
|
import {AuthService} from '../../services/auth.service';
|
||||||
|
import {UserRegisteredClaims} from '../../models/fasten/user-registered-claims';
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-header',
|
selector: 'app-header',
|
||||||
templateUrl: './header.component.html',
|
templateUrl: './header.component.html',
|
||||||
styleUrls: ['./header.component.scss']
|
styleUrls: ['./header.component.scss']
|
||||||
})
|
})
|
||||||
export class HeaderComponent implements OnInit {
|
export class HeaderComponent implements OnInit {
|
||||||
current_user: string
|
current_user_claims: UserRegisteredClaims
|
||||||
constructor(private authService: AuthService, private router: Router) { }
|
constructor(private authService: AuthService, private router: Router) { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
try {
|
try {
|
||||||
this.current_user = this.authService.GetCurrentUser()
|
this.current_user_claims = this.authService.GetCurrentUser()
|
||||||
} catch(e){
|
} catch(e){
|
||||||
this.current_user = "unknown"
|
this.current_user_claims = new UserRegisteredClaims()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
export class UserRegisteredClaims {
|
||||||
|
iss: string //Issuer
|
||||||
|
sub: string //Subject
|
||||||
|
aud?: string //Audience
|
||||||
|
exp: number //ExpiresAt (unix timestamp)
|
||||||
|
nbf?: number //NotBefore (unix timestamp)
|
||||||
|
iat?: number //IssuedAt (unix timestamp)
|
||||||
|
id?: string //ID
|
||||||
|
|
||||||
|
full_name: string //FullName
|
||||||
|
picture: string //Picture
|
||||||
|
email: string //Email
|
||||||
|
}
|
|
@ -40,6 +40,7 @@ export class AuthSigninComponent implements OnInit {
|
||||||
this.authService.IdpCallback(idpType, state, code)
|
this.authService.IdpCallback(idpType, state, code)
|
||||||
.then(() => this.router.navigateByUrl('/dashboard'))
|
.then(() => this.router.navigateByUrl('/dashboard'))
|
||||||
.catch((err)=>{
|
.catch((err)=>{
|
||||||
|
console.error("idpCallback error:", err)
|
||||||
const toastNotification = new ToastNotification()
|
const toastNotification = new ToastNotification()
|
||||||
toastNotification.type = ToastType.Error
|
toastNotification.type = ToastType.Error
|
||||||
toastNotification.message = "an error occurred while signing in"
|
toastNotification.message = "an error occurred while signing in"
|
||||||
|
|
|
@ -20,6 +20,20 @@
|
||||||
|
|
||||||
<form (ngSubmit)="signupSubmit()" #userForm="ngForm">
|
<form (ngSubmit)="signupSubmit()" #userForm="ngForm">
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label>Firstname & Lastname</label>
|
||||||
|
<input [(ngModel)]="newUser.full_name" name="full_name" #full_name="ngModel" required minlength="2" type="text" class="form-control" placeholder="Enter your firstname and lastname">
|
||||||
|
|
||||||
|
<div *ngIf="full_name.invalid && (full_name.dirty || full_name.touched)" class="alert alert-danger">
|
||||||
|
<div *ngIf="full_name.errors?.['required']">
|
||||||
|
Name is required.
|
||||||
|
</div>
|
||||||
|
<div *ngIf="full_name.errors?.['minlength']">
|
||||||
|
Name must be at least 4 characters long.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div><!-- form-group -->
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
<input [(ngModel)]="newUser.username" name="username" #username="ngModel" required minlength="5" type="text" class="form-control" placeholder="Enter your username">
|
<input [(ngModel)]="newUser.username" name="username" #username="ngModel" required minlength="5" type="text" class="form-control" placeholder="Enter your username">
|
||||||
|
|
|
@ -9,6 +9,7 @@ import * as Oauth from '@panva/oauth4webapi';
|
||||||
import {SourceState} from '../models/fasten/source-state';
|
import {SourceState} from '../models/fasten/source-state';
|
||||||
import {Session} from '../models/database/session';
|
import {Session} from '../models/database/session';
|
||||||
import * as jose from 'jose';
|
import * as jose from 'jose';
|
||||||
|
import {UserRegisteredClaims} from '../models/fasten/user-registered-claims';
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root'
|
providedIn: 'root'
|
||||||
|
@ -161,7 +162,7 @@ export class AuthService {
|
||||||
return localStorage.getItem(this.FASTEN_JWT_LOCALSTORAGE_KEY);
|
return localStorage.getItem(this.FASTEN_JWT_LOCALSTORAGE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
public GetCurrentUser(): string {
|
public GetCurrentUser(): UserRegisteredClaims {
|
||||||
let authToken = this.GetAuthToken()
|
let authToken = this.GetAuthToken()
|
||||||
if(!authToken){
|
if(!authToken){
|
||||||
throw new Error("no auth token found")
|
throw new Error("no auth token found")
|
||||||
|
@ -169,7 +170,8 @@ export class AuthService {
|
||||||
|
|
||||||
//parse the authToken to get user information
|
//parse the authToken to get user information
|
||||||
let jwtClaims = jose.decodeJwt(authToken)
|
let jwtClaims = jose.decodeJwt(authToken)
|
||||||
return jwtClaims.sub
|
// @ts-ignore
|
||||||
|
return jwtClaims as UserRegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Logout(): Promise<any> {
|
public async Logout(): Promise<any> {
|
||||||
|
|
|
@ -63,7 +63,7 @@ export class FastenDbService extends PouchdbRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
//parse the authToken to get user information
|
//parse the authToken to get user information
|
||||||
this.current_user = this.authService.GetCurrentUser()
|
this.current_user = this.authService.GetCurrentUser().sub
|
||||||
|
|
||||||
// add JWT bearer token header to all requests
|
// add JWT bearer token header to all requests
|
||||||
// https://stackoverflow.com/questions/62129654/how-to-handle-jwt-authentication-with-rxdb
|
// https://stackoverflow.com/questions/62129654/how-to-handle-jwt-authentication-with-rxdb
|
||||||
|
|
|
@ -20,7 +20,7 @@ export class QueueService {
|
||||||
if (typeof Worker !== 'undefined') {
|
if (typeof Worker !== 'undefined') {
|
||||||
const sourceSync = new SourceSyncMessage()
|
const sourceSync = new SourceSyncMessage()
|
||||||
sourceSync.source = source
|
sourceSync.source = source
|
||||||
sourceSync.current_user = this.authService.GetCurrentUser()
|
sourceSync.current_user = this.authService.GetCurrentUser().sub
|
||||||
sourceSync.auth_token = this.authService.GetAuthToken()
|
sourceSync.auth_token = this.authService.GetAuthToken()
|
||||||
sourceSync.couchdb_endpoint_base = environment.couchdb_endpoint_base
|
sourceSync.couchdb_endpoint_base = environment.couchdb_endpoint_base
|
||||||
sourceSync.fasten_api_endpoint_base = environment.fasten_api_endpoint_base
|
sourceSync.fasten_api_endpoint_base = environment.fasten_api_endpoint_base
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 16 KiB |
Binary file not shown.
|
@ -1,4 +1,5 @@
|
||||||
export class User {
|
export class User {
|
||||||
|
full_name?: string
|
||||||
username?: string
|
username?: string
|
||||||
password?: string
|
password?: string
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue