Skip to content

Commit 4a82064

Browse files
authored
add basic user management (#503)
* add basic user management * allow admin to create another admin * fix tests * add multi user info to README
1 parent fa0decc commit 4a82064

35 files changed

+678
-94
lines changed

README.md

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,10 @@
1313

1414
**Fasten securely connects your healthcare providers together, creating a personal health record that never leaves your hands**
1515

16-
> [!NOTE]
16+
> [!NOTE]
1717
> NOTE: Fasten is a Work-in-Progress and can only communicate with a limited number of Healthcare Instutions (approx 25,000 at last count).
1818
> Please fill out this [Google Form](https://forms.gle/SNsYX9BNMXB6TuTw6) if you'd like to be kept up-to-date on Fasten
1919
20-
> [!IMPORTANT]
21-
> To ensure Fasten's long-term sustainability, we're exploring some funding options. While we're still deciding a long-term monetization strategy, I'm kicking off with a crowdfunding/fundraising experiment for the first 500 users (including a surprise desktop app):
22-
>
23-
> - [Fasten Self-Hosted Lifetime License - **$200**](https://buy.stripe.com/fZe00deiUexS58Y4gg)
24-
>
25-
> Got questions or want to learn more about our fundraising experiment? [Click here to dive into the details & FAQs](https://docs.fastenhealth.com/funding.html)
26-
2720

2821
<p align="center">
2922
<br/>
@@ -49,19 +42,19 @@
4942

5043
# Introduction
5144

52-
Like many of you, I've worked for many companies over my career. In that time, I've had multiple health, vision and dental
45+
Like many of you, I've worked for many companies over my career. In that time, I've had multiple health, vision and dental
5346
insurance providers, and visited many different clinics, hospitals and labs to get procedures & tests done.
5447

55-
Recently I had a semi-serious medical issue, and I realized that my medical history (and the medical history of my family members)
56-
is a lot more complicated than I realized and distributed across the many healthcare providers I've used over the years.
48+
Recently I had a semi-serious medical issue, and I realized that my medical history (and the medical history of my family members)
49+
is a lot more complicated than I realized and distributed across the many healthcare providers I've used over the years.
5750
I wanted a single (private) location to store our medical records, and I just couldn't find any software that worked as I'd like:
5851

59-
- self-hosted/offline - this is my medical history, I'm not willing to give it to some random multi-national corporation to data-mine and sell
60-
- It should aggregate my data from multiple healthcare providers (insurance companies, hospital networks, clinics, labs) across multiple industries (vision, dental, medical) -- all in one dashboard
52+
- self-hosted/offline - this is my medical history, I'm not willing to give it to some random multi-national corporation to data-mine and sell
53+
- It should aggregate my data from multiple healthcare providers (insurance companies, hospital networks, clinics, labs) across multiple industries (vision, dental, medical) -- all in one dashboard
6154
- automatic - it should pull my EMR (electronic medical record) directly from my insurance provider/clinic/hospital network - I dont want to scan/OCR physical documents (unless I have to)
6255
- open source - the code should be available for contributions & auditing
6356

64-
So, I built it
57+
So, I built it.
6558

6659
**Fasten is an open-source, self-hosted, personal/family electronic medical record aggregator, designed to integrate with 1000's of insurances/hospitals/clinics**
6760

@@ -74,9 +67,9 @@ It's pretty basic right now, but it's designed with a easily extensible core aro
7467
- Supports the Medical industry's (semi-standard) FHIR protocol
7568
- Uses OAuth2 (Smart-on-FHIR) authentication (no passwords necessary)
7669
- Uses OAuth's `offline_access` scope (where possible) to automatically pull changes/updates
77-
- Multi-user support for household/family use
70+
- (Future) Multi-user support for household/family use
7871
- Condition specific user Dashboards & tracking for diagnostic tests
79-
- (Future) Vaccination & condition specific recommendations using NIH/WHO clinical care guidelines (HEDIS/CQL)
72+
- (Future) Vaccination & condition specific recommendations using NIH/WHO clinical care guidelines (HEDIS/CQL)
8073
- (Future) ChatGPT-style interface to query your own medical history (offline)
8174
- (Future) Integration with smart-devices & wearables
8275

@@ -95,13 +88,13 @@ First, if you don't have Docker installed on your computer, get Docker by follow
9588
Next, run the following commands from the Windows command line or Mac/Linux terminal in order to download and start the Fasten docker container.
9689

9790
```
98-
docker pull ghcr.io/fastenhealth/fasten-onprem:main
91+
docker pull ghcr.io/fastenhealth/fasten-onprem:main
9992
10093
docker run --rm \
10194
-p 9090:8080 \
10295
-v ./db:/opt/fasten/db \
10396
-v ./cache:/opt/fasten/cache \
104-
ghcr.io/fastenhealth/fasten-onprem:main
97+
ghcr.io/fastenhealth/fasten-onprem:main
10598
```
10699

107100
Next, open a browser to `http://localhost:9090`
@@ -123,6 +116,26 @@ If you're using the `sandbox` version of Fasten, you'll only be able to connect
123116

124117
https://docs.fastenhealth.com/getting-started/sandbox.html#connecting-a-new-source
125118

119+
## Using with multiple people
120+
121+
> [!NOTE]
122+
> NOTE: Multi-user features are a work in progress. This section describes the eventual goals.
123+
124+
Fasten is designd to work well for an individual or a family. Since it is self-hosted, by nature the person running the service will have full root access to all user records. For most families, this is perfect! If you need stronger security, Fasten might not be for you.
125+
126+
Fasten assumes that all records connected from a single user account (from one or more sources) belong to a single individual, and thus will show aggregations that will only make sense for a single person. Be careful to not connect sources for different people to the same Fasten user account.
127+
128+
Tracking health data for multiple family members works by creating new user accounts for each person. Any user with the `admin` role can manage users and permissions. Any user can be granted access (by an admin) to view another user's records. Through this mechanism, it's easy to setup any family configuration needed. For example: a family of four can have two parents that can each see the records of the two children.
129+
130+
It is also possible to create users with the `viewer` role that only have access to view records of other users. This can be used to share records with a caregiver.
131+
132+
This allows for a more complex example:
133+
134+
- a family consisting of 2 parents, and 2 children and a caregiver (nurse, babysitter, grandparent).
135+
- both parents need to be able to access both children's records, and maybe each-others
136+
- the caregiver should have view-only access to 1 or both children, but not the parents.
137+
138+
126139
# FAQ's
127140

128141
See [FAQs](https://docs.fastenhealth.com/faqs.html) for common questions (& answers) regarding Fasten
@@ -161,11 +174,9 @@ Jason Kulatunga - Initial Development - @AnalogJ
161174

162175
# Fundraising & Sponsorships
163176

164-
To ensure Fasten's long-term sustainability, we're exploring some funding options. While we're still deciding a long-term monetization strategy, I'm kicking off with a crowdfunding/fundraising experiment for the first 500 users (including a surprise desktop app):
165-
166-
- [Fasten Self-Hosted Lifetime License - **$200**](https://buy.stripe.com/fZe00deiUexS58Y4gg)
177+
To ensure Fasten's long-term sustainability, we're exploring some funding options. We're still deciding a long-term monetization strategy.
167178

168-
Got questions or want to learn more about our fundraising experiment? [Click here to dive into the details & FAQs](https://docs.fastenhealth.com/FUNDRAISING.html)
179+
Got questions or want to learn more about our fundraising experiment? [Click here to dive into the details & FAQs](https://docs.fastenhealth.com/FUNDRAISING.html)
169180

170181
I'd also like to thank the following Corporate Sponsors:
171182

backend/pkg/auth/jwt_utils.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ package auth
33
import (
44
"errors"
55
"fmt"
6-
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
7-
"github.com/golang-jwt/jwt/v4"
86
"strings"
97
"time"
8+
9+
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
10+
"github.com/golang-jwt/jwt/v4"
1011
)
1112

1213
// JwtGenerateFastenTokenFromUser Note: these functions are duplicated, in Fasten Cloud
13-
//Any changes here must be replicated in that repo
14+
// Any changes here must be replicated in that repo
1415
func JwtGenerateFastenTokenFromUser(user models.User, issuerSigningKey string) (string, error) {
1516
if len(strings.TrimSpace(issuerSigningKey)) == 0 {
1617
return "", fmt.Errorf("issuer signing key cannot be empty")
@@ -26,8 +27,8 @@ func JwtGenerateFastenTokenFromUser(user models.User, issuerSigningKey string) (
2627
},
2728
UserMetadata: UserMetadata{
2829
FullName: user.FullName,
29-
Picture: "",
30-
Email: user.ID.String(),
30+
Email: user.Email,
31+
Role: user.Role,
3132
},
3233
}
3334

backend/pkg/auth/user_metadata.go

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,12 @@
11
package auth
22

3+
import (
4+
"github.com/fastenhealth/fasten-onprem/backend/pkg"
5+
)
6+
37
type UserMetadata struct {
4-
FullName string `json:"full_name"`
5-
Picture string `json:"picture"`
6-
Email string `json:"email"`
8+
FullName string `json:"full_name"`
9+
Picture string `json:"picture"`
10+
Email string `json:"email"`
11+
Role pkg.UserRole `json:"role"`
712
}

backend/pkg/constants.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ type DatabaseRepositoryType string
99

1010
type InstallationVerificationStatus string
1111
type InstallationQuotaStatus string
12+
type UserRole string
1213

1314
const (
1415
ResourceListPageSize int = 20
@@ -50,4 +51,7 @@ const (
5051
InstallationVerificationStatusVerified InstallationVerificationStatus = "VERIFIED" //email has been verified
5152
InstallationQuotaStatusActive InstallationQuotaStatus = "ACTIVE"
5253
InstallationQuotaStatusConsumed InstallationQuotaStatus = "CONSUMED"
54+
55+
UserRoleUser UserRole = "user"
56+
UserRoleAdmin UserRole = "admin"
5357
)

backend/pkg/database/gorm_common.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ import (
55
"encoding/json"
66
"errors"
77
"fmt"
8-
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
98
"strings"
109
"time"
1110

11+
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
12+
1213
"github.com/fastenhealth/fasten-onprem/backend/pkg"
1314
"github.com/fastenhealth/fasten-onprem/backend/pkg/config"
1415
"github.com/fastenhealth/fasten-onprem/backend/pkg/event_bus"
@@ -179,6 +180,18 @@ func (gr *GormRepository) DeleteCurrentUser(ctx context.Context) error {
179180
return nil
180181
}
181182

183+
func (gr *GormRepository) GetUsers(ctx context.Context) ([]models.User, error) {
184+
var users []models.User
185+
result := gr.GormClient.WithContext(ctx).Find(&users)
186+
// Remove password field from each user
187+
var sanitizedUsers []models.User
188+
for _, user := range users {
189+
user.Password = "" // Clear the password field
190+
sanitizedUsers = append(sanitizedUsers, user)
191+
}
192+
return sanitizedUsers, result.Error
193+
}
194+
182195
//</editor-fold>
183196

184197
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

backend/pkg/database/gorm_repository_migrations.go

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,19 +3,21 @@ package database
33
import (
44
"context"
55
"fmt"
6+
"log"
7+
68
_20231017112246 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20231017112246"
79
_20231201122541 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20231201122541"
810
_0240114092806 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240114092806"
911
_20240114103850 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240114103850"
1012
_20240208112210 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240208112210"
13+
_20240813222836 "github.com/fastenhealth/fasten-onprem/backend/pkg/database/migrations/20240813222836"
1114
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
1215
databaseModel "github.com/fastenhealth/fasten-onprem/backend/pkg/models/database"
1316
sourceCatalog "github.com/fastenhealth/fasten-sources/catalog"
1417
sourcePkg "github.com/fastenhealth/fasten-sources/pkg"
1518
"github.com/go-gormigrate/gormigrate/v2"
1619
"github.com/google/uuid"
1720
"gorm.io/gorm"
18-
"log"
1921
)
2022

2123
func (gr *GormRepository) Migrate() error {
@@ -194,6 +196,35 @@ func (gr *GormRepository) Migrate() error {
194196
return nil
195197
},
196198
},
199+
{
200+
ID: "20240813222836", // add role to user
201+
Migrate: func(tx *gorm.DB) error {
202+
203+
err := tx.AutoMigrate(
204+
&_20240813222836.User{},
205+
)
206+
if err != nil {
207+
return err
208+
}
209+
210+
// set first user to admin
211+
// set all other users to user
212+
users := []_20240813222836.User{}
213+
results := tx.Order("created_at ASC").Find(&users)
214+
if results.Error != nil {
215+
return results.Error
216+
}
217+
for ndx, user := range users {
218+
if ndx == 0 {
219+
user.Role = _20240813222836.RoleAdmin
220+
} else {
221+
user.Role = _20240813222836.RoleUser
222+
}
223+
tx.Save(&user)
224+
}
225+
return nil
226+
},
227+
},
197228
})
198229

199230
// run when database is empty

backend/pkg/database/interface.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package database
22

33
import (
44
"context"
5+
56
"github.com/fastenhealth/fasten-onprem/backend/pkg"
67
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
78
sourcePkg "github.com/fastenhealth/fasten-sources/clients/models"
@@ -18,6 +19,7 @@ type DatabaseRepository interface {
1819
GetUserByUsername(context.Context, string) (*models.User, error)
1920
GetCurrentUser(ctx context.Context) (*models.User, error)
2021
DeleteCurrentUser(ctx context.Context) error
22+
GetUsers(ctx context.Context) ([]models.User, error)
2123

2224
GetSummary(ctx context.Context) (*models.Summary, error)
2325

backend/pkg/database/migrations/20231017112246/user.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,4 @@ type User struct {
1313
//additional optional metadata that Fasten stores with users
1414
Picture string `json:"picture"`
1515
Email string `json:"email"`
16-
//Roles datatypes.JSON `json:"roles"`
1716
}

backend/pkg/database/migrations/20231201122541/user.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,4 @@ type User struct {
1313
//additional optional metadata that Fasten stores with users
1414
Picture string `json:"picture"`
1515
Email string `json:"email"`
16-
//Roles datatypes.JSON `json:"roles"`
1716
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package _20240813222836
2+
3+
import (
4+
"github.com/fastenhealth/fasten-onprem/backend/pkg/models"
5+
)
6+
7+
type Role string
8+
9+
const (
10+
RoleUser Role = "user"
11+
RoleAdmin Role = "admin"
12+
)
13+
14+
type User struct {
15+
models.ModelBase
16+
FullName string `json:"full_name"`
17+
Username string `json:"username" gorm:"unique"`
18+
Password string `json:"password"`
19+
20+
//additional optional metadata that Fasten stores with users
21+
Picture string `json:"picture"`
22+
Email string `json:"email"`
23+
Role Role `json:"role"`
24+
}

0 commit comments

Comments
 (0)