Skip to content

Commit

Permalink
working backend changes to generate JWT tokens during signin and sign…
Browse files Browse the repository at this point in the history
  • Loading branch information
AnalogJ committed Nov 2, 2022
1 parent 9d56fa2 commit 0329461
Show file tree
Hide file tree
Showing 26 changed files with 337 additions and 178 deletions.
12 changes: 12 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,3 +220,15 @@ a CDN or minimal Nginx deployment.
- FASTEN_ISSUER_JWT_KEY
- FASTEN_COUCHDB_ADMIN_USERNAME
- FASTEN_COUCHDB_ADMIN_PASSWORD


### Generate JWT for local use
```bash
curl -X POST https://localhost:9090/api/auth/signup -H 'Content-Type: application/json' -d '{"username":"user1","password":"user1"}'

curl -X POST https://localhost:9090/api/auth/signin -H 'Content-Type: application/json' -d '{"username":"user1","password":"user1"}'


curl -H "Authorization: Bearer ${JWT_TOKEN_HERE}" https://localhost:5984/_session

```
2 changes: 2 additions & 0 deletions backend/pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ func (c *configuration) Init() error {
c.SetDefault("couchdb.admin.username", "admin")
c.SetDefault("couchdb.admin.password", "mysecretpassword")

c.SetDefault("jwt.issuer.key", "thisismysupersecuressessionsecretlength")

c.SetDefault("log.level", "INFO")
c.SetDefault("log.file", "")

Expand Down
28 changes: 28 additions & 0 deletions backend/pkg/database/couchdb_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
_ "github.com/go-kivik/couchdb/v3" // The CouchDB driver
"github.com/go-kivik/kivik/v3"
"github.com/sirupsen/logrus"
"log"
)

func NewRepository(appConfig config.Interface, globalLogger logrus.FieldLogger) (DatabaseRepository, error) {
Expand Down Expand Up @@ -69,9 +70,36 @@ func (cr *couchdbRepository) CreateUser(ctx context.Context, user *models.User)
}
db := cr.client.DB(ctx, "_users")
_, err := db.Put(ctx, newUser.ID, newUser)
if err != nil {
return err
}

//TODO: we should create an index for this database now
//db.CreateIndex(ctx, )
return err
}

func (cr *couchdbRepository) VerifyUser(ctx context.Context, user *models.User) error {

couchdbUrl := fmt.Sprintf("%s:https://%s:%s", cr.appConfig.GetString("couchdb.scheme"), cr.appConfig.GetString("couchdb.host"), cr.appConfig.GetString("couchdb.port"))

userDatabase, err := kivik.New("couch", couchdbUrl)
if err != nil {
return fmt.Errorf("Failed to connect to database! - %v", err)
}

err = userDatabase.Authenticate(context.Background(),
couchdb.BasicAuth(user.Username, user.Password),
)
session, err := userDatabase.Session(context.Background())
if err != nil {
return err
}
log.Printf("SESSION INFO: %v", session)
//TODO: return session info
return nil
}

func (cr *couchdbRepository) Close() error {
return nil
}
1 change: 1 addition & 0 deletions backend/pkg/database/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ type DatabaseRepository interface {
Close() error

CreateUser(context.Context, *models.User) error
VerifyUser(context.Context, *models.User) error
}
46 changes: 46 additions & 0 deletions backend/pkg/web/handler/auth.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package handler

import (
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/config"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/database"
"github.com/fastenhealth/fastenhealth-onprem/backend/pkg/models"
"github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v4"
"log"
"net/http"
"time"
)

func AuthSignup(c *gin.Context) {
Expand All @@ -23,3 +27,45 @@ func AuthSignup(c *gin.Context) {

c.JSON(http.StatusOK, gin.H{"success": true})
}

func AuthSignin(c *gin.Context) {
databaseRepo := c.MustGet("REPOSITORY").(database.DatabaseRepository)
appConfig := c.MustGet("CONFIG").(config.Interface)

var user models.User
if err := c.ShouldBindJSON(&user); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}
err := databaseRepo.VerifyUser(c, &user)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"success": false, "error": err.Error()})
return
}

//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 jwtGenerateFastenTokenFromUser(user models.User, issuerSigningKey string) (string, error) {
log.Printf("ISSUER KEY: " + issuerSigningKey)
userClaims := jwt.RegisteredClaims{
// In JWT, the expiry time is expressed as unix milliseconds
ExpiresAt: jwt.NewNumericDate(time.Now().Add(1 * time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "docker-fastenhealth",
Subject: user.Username,
}

//FASTEN_JWT_ISSUER_KEY
token := jwt.NewWithClaims(jwt.SigningMethodHS256, userClaims)
//token.Header["kid"] = "docker"
tokenString, err := token.SignedString([]byte(issuerSigningKey))

if err != nil {
return "", err
}
return tokenString, nil
}
1 change: 1 addition & 0 deletions backend/pkg/web/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (ae *AppEngine) Setup(logger *logrus.Entry) *gin.Engine {
})

api.POST("/auth/signup", handler.AuthSignup)
api.POST("/auth/signin", handler.AuthSignin)

r.Any("/database/*proxyPath", handler.CouchDBProxy)
r.GET("/cors/*proxyPath", handler.CORSProxy)
Expand Down
2 changes: 1 addition & 1 deletion docker/couchdb/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ FROM couchdb:3.2

ENV FASTEN_COUCHDB_ADMIN_USERNAME=admin
ENV FASTEN_COUCHDB_ADMIN_PASSWORD=mysecretpassword
ENV FASTEN_ISSUER_JWT_KEY=mysessionpassword
ENV FASTEN_JWT_ISSUER_KEY=thisismysupersecuressessionsecretlength

ARG S6_ARCH=amd64
RUN curl https://github.com/just-containers/s6-overlay/releases/download/v1.21.8.0/s6-overlay-${S6_ARCH}.tar.gz -L -s --output /tmp/s6-overlay-${S6_ARCH}.tar.gz \
Expand Down
9 changes: 5 additions & 4 deletions docker/rootfs/etc/cont-init.d/05-couchdb-config
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
if [ -f "/opt/couchdb/data/.config_complete" ]; then
echo "Couchdb config has already completed, skipping"
else
#echo -n means we dont pass newline to base64 (ugh). eg. hello = aGVsbG8K vs aGVsbG8=
FASTEN_JWT_ISSUER_KEY_BASE64=$(echo -n "${FASTEN_JWT_ISSUER_KEY}" | base64)

FASTEN_ISSUER_JWT_KEY_BASE64=$(echo "${FASTEN_ISSUER_JWT_KEY}" | base64)


cat << EOF >> /opt/couchdb/etc/local.ini
cat << EOF >> /opt/couchdb/etc/local.d/generated.ini

; ------------------------------------------ GENERATED MODIFICATIONS
; ------------------------------------------ GENERATED MODIFICATIONS
Expand All @@ -17,12 +17,13 @@ cat << EOF >> /opt/couchdb/etc/local.ini
required_claims = exp, {iss, "docker-fastenhealth"}

[jwt_keys]
hmac:_default = ${FASTEN_ISSUER_JWT_KEY_BASE64}
hmac:_default = ${FASTEN_JWT_ISSUER_KEY_BASE64}


; users should change this default password
[admins]
${FASTEN_COUCHDB_ADMIN_USERNAME} = ${FASTEN_COUCHDB_ADMIN_PASSWORD}

EOF

# create the config complete flag
Expand Down
1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"garbados-crypt": "^3.0.0-beta",
"humanize-duration": "^3.27.3",
"idb": "^7.1.0",
"jose": "^4.10.4",
"moment": "^2.29.4",
"ng2-charts": "^2.3.0",
"ngx-dropzone": "^3.1.0",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import { HighlightModule, HIGHLIGHT_OPTIONS } from 'ngx-highlightjs';
import {AuthInterceptorService} from './services/auth-interceptor.service';
import { MomentModule } from 'ngx-moment';
import { EncryptionManagerComponent } from './pages/encryption-manager/encryption-manager.component';
import {AuthService} from './services/auth.service';

@NgModule({
declarations: [
Expand Down Expand Up @@ -59,7 +60,7 @@ import { EncryptionManagerComponent } from './pages/encryption-manager/encryptio
provide: HTTP_INTERCEPTORS,
useClass: AuthInterceptorService,
multi: true,
deps: [FastenDbService, Router]
deps: [AuthService, Router]
},
IsAuthenticatedAuthGuard,
EncryptionEnabledAuthGuard,
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/app/auth-guards/is-authenticated-auth-guard.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Injectable } from '@angular/core';
import {CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree, Router} from '@angular/router';
import { FastenDbService } from '../services/fasten-db.service';
import {AuthService} from '../services/auth.service';

@Injectable()
export class IsAuthenticatedAuthGuard implements CanActivate {
constructor(private fastenDbService: FastenDbService, private router: Router) {
constructor(private authService: AuthService, private router: Router) {

}

async canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Promise <boolean> {
//check if the user is authenticated, if not, redirect to login
if (! await this.fastenDbService.IsAuthenticated()) {
if (! await this.authService.IsAuthenticated()) {
return await this.router.navigate(['/auth/signin']);
}
// continue as normal
Expand Down
7 changes: 4 additions & 3 deletions frontend/src/app/components/header/header.component.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { Component, OnInit } from '@angular/core';
import {FastenDbService} from '../../services/fasten-db.service';
import { Router } from '@angular/router';
import {AuthService} from '../../services/auth.service';
@Component({
selector: 'app-header',
templateUrl: './header.component.html',
styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
current_user: string
constructor(private fastenDb: FastenDbService, private router: Router) { }
constructor(private authService: AuthService, private router: Router) { }

ngOnInit() {
this.current_user = this.fastenDb.current_user
this.current_user = this.authService.GetCurrentUser()
}

closeMenu(e) {
Expand All @@ -25,7 +26,7 @@ export class HeaderComponent implements OnInit {
}

signOut(e) {
this.fastenDb.Logout()
this.authService.Logout()
.then(() => this.router.navigate(['auth/signin']))
}
}
15 changes: 15 additions & 0 deletions frontend/src/app/models/database/session.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface Session {
ok: boolean;
userCtx?: UserCtx;
info?: Info;
}

export interface UserCtx {
name?: any;
roles?: any[];
}
export interface Info {
authenticated?: string
authentication_handlers: string[];
}

1 change: 1 addition & 0 deletions frontend/src/app/models/queue/source-sync-message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {Source} from '../../../lib/models/database/source';
export class SourceSyncMessage {
source: Source
current_user: string
auth_token: string
couchdb_endpoint_base: string
fasten_api_endpoint_base: string
response?: any
Expand Down
11 changes: 9 additions & 2 deletions frontend/src/app/pages/auth-signin/auth-signin.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,15 +37,22 @@ export class AuthSigninComponent implements OnInit {
let state = params.get('state') // eyJhbGciOiJSUzI1...rest_of_ID_Token

this.resetUrlOnCallback()
this.authService.IdpCallback(idpType, state, code).then(console.log)
this.authService.IdpCallback(idpType, state, code)
.then(() => this.router.navigateByUrl('/dashboard'))
.catch((err)=>{
const toastNotification = new ToastNotification()
toastNotification.type = ToastType.Error
toastNotification.message = "an error occurred while signing in"
this.toastService.show(toastNotification)
})
}

}

signinSubmit(){
this.submitted = true;

this.fastenDb.Signin(this.existingUser.username, this.existingUser.password)
this.authService.Signin(this.existingUser.username, this.existingUser.password)
.then(() => this.router.navigateByUrl('/dashboard'))
.catch((err)=>{
if(err?.name){
Expand Down
5 changes: 3 additions & 2 deletions frontend/src/app/pages/auth-signup/auth-signup.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {User} from '../../../lib/models/fasten/user';
import {Router} from '@angular/router';
import {ToastNotification, ToastType} from '../../models/fasten/toast';
import {ToastService} from '../../services/toast.service';
import {AuthService} from '../../services/auth.service';

@Component({
selector: 'app-auth-signup',
Expand All @@ -16,7 +17,7 @@ export class AuthSignupComponent implements OnInit {
errorMsg: string = ""

constructor(
private fastenDb: FastenDbService,
private authService: AuthService,
private router: Router,
private toastService: ToastService
) { }
Expand All @@ -27,7 +28,7 @@ export class AuthSignupComponent implements OnInit {
signupSubmit(){
this.submitted = true;

this.fastenDb.Signup(this.newUser).then((tokenResp: any) => {
this.authService.Signup(this.newUser).then((tokenResp: any) => {
console.log(tokenResp);
this.router.navigateByUrl('/dashboard');
},
Expand Down
Loading

0 comments on commit 0329461

Please sign in to comment.