-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit f881f9a
Showing
47 changed files
with
17,675 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
node_modules | ||
.idea | ||
web/dist | ||
data | ||
|
||
# mac | ||
.DS_Store |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
# | ||
# Build frontend | ||
# | ||
FROM node:lts-alpine as frontend-builder | ||
WORKDIR /app | ||
COPY package*.json ./ | ||
RUN npm install | ||
COPY ./web ./web | ||
COPY ./*.config.js . | ||
RUN npm run build | ||
|
||
# | ||
# Build backend | ||
# | ||
FROM golang:1.21-alpine as backend-builder | ||
|
||
ARG TARGETARCH | ||
ARG TARGETOS | ||
|
||
ENV GO111MODULE on | ||
ENV GOPATH / | ||
|
||
RUN mkdir /app && cd /app | ||
WORKDIR /app | ||
COPY go.mod . | ||
COPY go.sum . | ||
RUN go mod download && go mod verify | ||
|
||
COPY . . | ||
COPY --from=frontend-builder /app/web/dist /app/web/dist | ||
RUN CGO_ENABLED=0 GOOS=$TARGETOS GOARCH=$TARGETARCH go build -ldflags="-w -s" -o /app/nuts-admin | ||
|
||
# | ||
# Runtime | ||
# | ||
FROM alpine:3.18 | ||
RUN mkdir /app && cd /app | ||
WORKDIR /app | ||
COPY --from=backend-builder /app/nuts-admin . | ||
HEALTHCHECK --start-period=5s --timeout=5s --interval=5s \ | ||
CMD wget --no-verbose --tries=1 --spider http://localhost:1305/status || exit 1 | ||
EXPOSE 1305 | ||
ENTRYPOINT ["/app/nuts-admin"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# nuts-admin | ||
Application which shows how to integrate with the Nuts node to administer identities. | ||
|
||
## Building and running | ||
|
||
### Development | ||
|
||
During front-end development, you probably want to use the real filesystem and webpack in watch mode: | ||
|
||
```shell | ||
make dev | ||
``` | ||
|
||
The API and domain types are generated from the `api/api.yaml`. | ||
```shell | ||
make gen-api | ||
``` | ||
|
||
### Docker | ||
```shell | ||
$ docker run -p 1305:1305 nutsfoundation/nuts-admin | ||
``` | ||
|
||
## Configuration | ||
When running in Docker without a config file mounted at `/app/config.yaml` it will use the default configuration. | ||
In this case the default username will be `demo@nuts.nl`. The password is generated and printed in the log on startup. | ||
|
||
The `node.auth.keyfile` config parameter should point to a PEM encoded private key file. The corresponding public key should be configured on the Nuts node in SSH authorized keys format. | ||
`node.auth.user` Is required when using Nuts node API token security. It must match the user in the SSH authorized keys file. | ||
|
||
## Technology Stack | ||
|
||
Frontend framework is vue.js 3.x | ||
|
||
Icons are from https://heroicons.com | ||
|
||
CSS framework is https://tailwindcss.com |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package api | ||
|
||
import ( | ||
"github.com/nuts-foundation/nuts-admin/identity" | ||
"net/http" | ||
|
||
"github.com/labstack/echo/v4" | ||
) | ||
|
||
var _ ServerInterface = (*Wrapper)(nil) | ||
|
||
type Wrapper struct { | ||
Auth auth | ||
Identity identity.Service | ||
} | ||
|
||
func (w Wrapper) GetIdentities(ctx echo.Context) error { | ||
identities, err := w.Identity.List(ctx.Request().Context()) | ||
if err != nil { | ||
return err | ||
} | ||
return ctx.JSON(http.StatusOK, identities) | ||
} | ||
|
||
func (w Wrapper) CreateIdentity(ctx echo.Context) error { | ||
identityRequest := CreateIdentityJSONRequestBody{} | ||
if err := ctx.Bind(&identityRequest); err != nil { | ||
return err | ||
} | ||
id, err := w.Identity.Create(ctx.Request().Context(), identityRequest.DidQualifier) | ||
if err != nil { | ||
return err | ||
} | ||
return ctx.JSON(http.StatusOK, id) | ||
} | ||
|
||
func (w Wrapper) CheckSession(ctx echo.Context) error { | ||
// If this function is reached, it means the session is still valid | ||
return ctx.NoContent(http.StatusNoContent) | ||
} | ||
|
||
func (w Wrapper) CreateSession(ctx echo.Context) error { | ||
sessionRequest := CreateSessionRequest{} | ||
if err := ctx.Bind(&sessionRequest); err != nil { | ||
return err | ||
} | ||
|
||
if !w.Auth.CheckCredentials(sessionRequest.Username, sessionRequest.Password) { | ||
return echo.NewHTTPError(http.StatusForbidden, "invalid credentials") | ||
} | ||
|
||
token, err := w.Auth.CreateJWT(sessionRequest.Username) | ||
if err != nil { | ||
return echo.NewHTTPError(http.StatusInternalServerError, err) | ||
} | ||
|
||
return ctx.JSON(http.StatusOK, CreateSessionResponse{Token: string(token)}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
openapi: 3.0.0 | ||
info: | ||
title: Nuts Admin API | ||
version: 1.0.0 | ||
|
||
paths: | ||
/web/auth: | ||
post: | ||
operationId: createSession | ||
requestBody: | ||
required: true | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/CreateSessionRequest" | ||
responses: | ||
'200': | ||
description: A session was succesfully created | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/CreateSessionResponse" | ||
'403': | ||
description: Invalid credentials | ||
|
||
/web/private: | ||
get: | ||
description: Checks whether the current session is valid. If not, the client should authenticate before calling other API operations. | ||
operationId: checkSession | ||
responses: | ||
'204': | ||
description: The session is valid. | ||
'400': | ||
description: The session is invalid. | ||
|
||
/web/private/id: | ||
get: | ||
operationId: getIdentities | ||
responses: | ||
'200': | ||
description: List of identities | ||
content: | ||
application/json: | ||
schema: | ||
type: array | ||
items: | ||
$ref: "#/components/schemas/Identity" | ||
post: | ||
operationId: createIdentity | ||
requestBody: | ||
required: true | ||
content: | ||
application/json: | ||
schema: | ||
type: object | ||
required: | ||
- did_qualifier | ||
properties: | ||
did_qualifier: | ||
type: string | ||
responses: | ||
'200': | ||
description: The identity was succesfully created | ||
content: | ||
application/json: | ||
schema: | ||
$ref: "#/components/schemas/Identity" | ||
'400': | ||
description: The identity could not be created | ||
|
||
|
||
components: | ||
schemas: | ||
CreateSessionRequest: | ||
required: | ||
- username | ||
- password | ||
properties: | ||
username: | ||
type: string | ||
example: demo@nuts.nl | ||
password: | ||
type: string | ||
CreateSessionResponse: | ||
required: | ||
- token | ||
properties: | ||
token: | ||
type: string | ||
Identity: | ||
type: object | ||
description: An identity object | ||
required: | ||
- did | ||
- name | ||
properties: | ||
did: | ||
type: string | ||
description: The DID associated with this identity | ||
example: | ||
"did:web:example.com:iam:user1" | ||
name: | ||
type: string | ||
description: | | ||
The name of this identity, which is the last path part of a did:web DID. | ||
If the DID does not contain paths, or it is not a did:web DID, it will be the same as the DID. | ||
example: "user1" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
package api | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"log" | ||
"time" | ||
|
||
"github.com/lestrrat-go/jwx/jwa" | ||
"github.com/lestrrat-go/jwx/jwt" | ||
"github.com/lestrrat-go/jwx/jwt/openid" | ||
) | ||
|
||
type UserAccount struct { | ||
Username string | ||
Password string | ||
} | ||
|
||
type auth struct { | ||
sessionKey *ecdsa.PrivateKey | ||
userAccounts []UserAccount | ||
} | ||
|
||
func NewAuth(key *ecdsa.PrivateKey, userAccounts []UserAccount) auth { | ||
return auth{ | ||
sessionKey: key, | ||
userAccounts: userAccounts, | ||
} | ||
} | ||
|
||
func (auth auth) CheckCredentials(username, password string) bool { | ||
for _, account := range auth.userAccounts { | ||
if account.Username == username && account.Password == password { | ||
return true | ||
} | ||
} | ||
return false | ||
} | ||
|
||
func (auth auth) CreateJWT(email string) ([]byte, error) { | ||
t := openid.New() | ||
t.Set(jwt.IssuedAtKey, time.Now()) | ||
// session is valid for 20 minutes | ||
t.Set(jwt.ExpirationKey, time.Now().Add(20*time.Minute)) | ||
t.Set(openid.EmailKey, email) | ||
|
||
signed, err := jwt.Sign(t, jwa.ES256, auth.sessionKey) | ||
if err != nil { | ||
log.Printf("failed to sign token: %s", err) | ||
return nil, err | ||
} | ||
return signed, nil | ||
} | ||
|
||
func (auth auth) ValidateJWT(token []byte) (jwt.Token, error) { | ||
pubKey := auth.sessionKey.PublicKey | ||
t, err := jwt.Parse(token, jwt.WithVerify(jwa.ES256, pubKey), jwt.WithValidate(true)) | ||
if err != nil { | ||
log.Printf("unable to parse token: %s", err) | ||
return nil, err | ||
} | ||
return t, nil | ||
} |
Oops, something went wrong.