31 Commits

Author SHA1 Message Date
e107d358ee Some cleanups, added logout hook 2022-09-10 01:34:34 +02:00
fc24ce1306 Authstate, Login, Logout. Store, dispatch etc 2022-09-09 12:39:10 +02:00
d527500034 FrontEnd: Playing with store (solidjs) 2022-09-09 10:39:19 +02:00
637dfb45d8 Email verification frontend. 2022-09-09 08:39:46 +02:00
65d62e8e49 migrations pull from supertokens 2022-09-09 01:32:24 +02:00
6d19022c64 removed .env. SMPT Supertokens. 2022-09-09 00:36:36 +02:00
fb0ca6a3ba Migrations! 2022-09-08 21:43:50 +02:00
2a70074382 Playing with Queries, and Playground 2022-09-08 20:39:24 +02:00
31e7aec64d Some refactoring. dev script, graphql dir. 2022-09-08 19:21:20 +02:00
1bcf265aec Prisma Generation client (npm script concurrently) 2022-09-08 14:39:04 +02:00
6f5cc1642a Prisma implementation... 2022-09-08 13:24:07 +02:00
cd28031637 Fontend supertokens, authprovider. 2022-09-08 11:35:19 +02:00
8bed3632a2 Prisma init. 2022-09-08 01:51:32 +02:00
e06850eec4 GraphQL Server. 2022-09-08 01:24:55 +02:00
48545030fd Removed some useless providers. 2022-09-07 13:28:15 +02:00
3ce2a12861 Removed JWK url. 2022-09-07 10:56:12 +02:00
9e76c140e4 Some refactoring. 2022-09-07 10:51:58 +02:00
37585ff9c4 Email, Password Supertokens Ok 2022-09-07 10:31:53 +02:00
c44be1bcc1 Implemented SuperTokens 2022-09-07 10:09:17 +02:00
6bb920861a switched to supertokens 2022-09-07 09:12:26 +02:00
40eb1da379 Axios again. Done. Registration flow seems ok. 2022-09-07 02:47:37 +02:00
8a9973565b GlobalFetch Api TS problem. Look around. 2022-09-06 20:30:03 +02:00
ec84da2f52 Switched kc_sdk to fetch 2022-09-05 17:02:45 +02:00
bb92669508 Tested Register. Done! 2022-09-05 15:46:17 +02:00
b4171e215d OpenApi Generator. Keycloak SDK done. Login Done! 2022-09-05 14:06:55 +02:00
5b9cbd62fd Login Endpoint 2022-09-05 12:31:03 +02:00
083b7177d3 Auth Resources 2022-09-05 10:27:36 +02:00
87aa032b6b Switched to NestJS 2022-09-05 09:51:34 +02:00
75e0ad2f6f . 2022-09-04 21:54:39 +02:00
ec92cc4787 Added Ts.ED (remaining) 2022-09-04 21:06:28 +02:00
f4162f7f06 Added Ts.ED 2022-09-04 21:00:45 +02:00
69 changed files with 6912 additions and 116 deletions

0
-I
View File

4
.gitignore vendored
View File

@@ -57,7 +57,7 @@ node_modules
/build
/.svelte-kit
/package
.env
#.env
.env.*
!.env.example
.vercel
@@ -65,3 +65,5 @@ node_modules
dist
.graphqlrc.yml
codegen.yml
backend/src/config/kc.config.json
backend/.env

25
backend/.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

35
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,35 @@
# compiled output
/dist
/node_modules
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

4
backend/.prettierrc Normal file
View File

@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": "all"
}

73
backend/README.md Normal file
View File

@@ -0,0 +1,73 @@
<p align="center">
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
</p>
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
[circleci-url]: https://circleci.com/gh/nestjs/nest
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
<p align="center">
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
</p>
<!--[![Backers on Open Collective](https://opencollective.com/nest/backers/badge.svg)](https://opencollective.com/nest#backer)
[![Sponsors on Open Collective](https://opencollective.com/nest/sponsors/badge.svg)](https://opencollective.com/nest#sponsor)-->
## Description
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
## Installation
```bash
$ npm install
```
## Running the app
```bash
# development
$ npm run start
# watch mode
$ npm run start:dev
# production mode
$ npm run start:prod
```
## Test
```bash
# unit tests
$ npm run test
# e2e tests
$ npm run test:e2e
# test coverage
$ npm run test:cov
```
## Support
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
## Stay in touch
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
- Website - [https://nestjs.com](https://nestjs.com/)
- Twitter - [@nestframework](https://twitter.com/nestframework)
## License
Nest is [MIT licensed](LICENSE).

View File

@@ -0,0 +1,11 @@
import { GraphQLDefinitionsFactory } from '@nestjs/graphql';
import { join } from 'path';
const definitionFactory = new GraphQLDefinitionsFactory();
definitionFactory.generate({
typePaths: ['./**/*.graphql'],
path: join(process.cwd(), 'src/graphql/graphql.typings.ts'),
outputAs: 'class',
watch: true,
});

View File

@@ -0,0 +1,35 @@
/*
* -------------------------------------------------------
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
* -------------------------------------------------------
*/
/* tslint:disable */
/* eslint-disable */
export class CreateUserInput {
email: string;
password: string;
}
export class User {
id: string;
email: string;
time_joined: number;
}
export abstract class IQuery {
abstract users(): Nullable<User>[] | Promise<Nullable<User>[]>;
abstract user(id: string): Nullable<User> | Promise<Nullable<User>>;
}
export abstract class IMutation {
abstract createUser(createUserInput: CreateUserInput): User | Promise<User>;
abstract removeUser(id: string): Nullable<User> | Promise<Nullable<User>>;
}
export type DateTime = any;
type Nullable<T> = T | null;

5
backend/nest-cli.json Normal file
View File

@@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

79
backend/package.json Normal file
View File

@@ -0,0 +1,79 @@
{
"name": "backend",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json",
"gen:types": "ts-node ./graphql/generate.typings",
"prisma:gen": "prisma generate --watch",
"dev": "concurrently \"npm:start:dev\" \"npm:gen:types\" \"npm:prisma:gen\""
},
"dependencies": {
"@nestjs/apollo": "^10.1.0",
"@nestjs/common": "^9.0.0",
"@nestjs/config": "^2.2.0",
"@nestjs/core": "^9.0.0",
"@nestjs/graphql": "^10.1.1",
"@nestjs/mapped-types": "*",
"@nestjs/platform-express": "^9.0.0",
"@prisma/client": "4.3.1",
"apollo-server-express": "^3.10.2",
"graphql": "^16.6.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.2.0",
"supertokens-node": "^11.3.0",
"ts-morph": "^16.0.0"
},
"devDependencies": {
"@nestjs/cli": "^9.0.0",
"@nestjs/schematics": "^9.0.0",
"@nestjs/testing": "^9.0.0",
"@types/express": "^4.17.13",
"@types/jest": "28.1.8",
"@types/node": "^16.0.0",
"@types/supertest": "^2.0.11",
"concurrently": "^7.4.0",
"jest": "28.1.3",
"prettier": "^2.3.2",
"prisma": "4.3.1",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"ts-jest": "28.0.8",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.1.0",
"typescript": "^4.7.4"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@@ -0,0 +1,11 @@
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"email" TEXT NOT NULL,
"time_joined" INTEGER NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "password" TEXT,
ALTER COLUMN "time_joined" DROP NOT NULL;

View File

@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "createdAt" TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN "updatedAt" TIMESTAMP(3);

View File

@@ -0,0 +1,245 @@
/*
Warnings:
- You are about to drop the `User` table. If the table is not empty, all the data it contains will be lost.
*/
-- DropTable
DROP TABLE "User";
-- CreateTable
CREATE TABLE "all_auth_recipe_users" (
"user_id" CHAR(36) NOT NULL,
"recipe_id" VARCHAR(128) NOT NULL,
"time_joined" BIGINT NOT NULL,
CONSTRAINT "all_auth_recipe_users_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "emailpassword_pswd_reset_tokens" (
"user_id" CHAR(36) NOT NULL,
"token" VARCHAR(128) NOT NULL,
"token_expiry" BIGINT NOT NULL,
CONSTRAINT "emailpassword_pswd_reset_tokens_pkey" PRIMARY KEY ("user_id","token")
);
-- CreateTable
CREATE TABLE "emailpassword_users" (
"user_id" CHAR(36) NOT NULL,
"email" VARCHAR(256) NOT NULL,
"password_hash" VARCHAR(128) NOT NULL,
"time_joined" BIGINT NOT NULL,
CONSTRAINT "emailpassword_users_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "emailverification_tokens" (
"user_id" VARCHAR(128) NOT NULL,
"email" VARCHAR(256) NOT NULL,
"token" VARCHAR(128) NOT NULL,
"token_expiry" BIGINT NOT NULL,
CONSTRAINT "emailverification_tokens_pkey" PRIMARY KEY ("user_id","email","token")
);
-- CreateTable
CREATE TABLE "emailverification_verified_emails" (
"user_id" VARCHAR(128) NOT NULL,
"email" VARCHAR(256) NOT NULL,
CONSTRAINT "emailverification_verified_emails_pkey" PRIMARY KEY ("user_id","email")
);
-- CreateTable
CREATE TABLE "jwt_signing_keys" (
"key_id" VARCHAR(255) NOT NULL,
"key_string" TEXT NOT NULL,
"algorithm" VARCHAR(10) NOT NULL,
"created_at" BIGINT,
CONSTRAINT "jwt_signing_keys_pkey" PRIMARY KEY ("key_id")
);
-- CreateTable
CREATE TABLE "key_value" (
"name" VARCHAR(128) NOT NULL,
"value" TEXT,
"created_at_time" BIGINT,
CONSTRAINT "key_value_pkey" PRIMARY KEY ("name")
);
-- CreateTable
CREATE TABLE "passwordless_codes" (
"code_id" CHAR(36) NOT NULL,
"device_id_hash" CHAR(44) NOT NULL,
"link_code_hash" CHAR(44) NOT NULL,
"created_at" BIGINT NOT NULL,
CONSTRAINT "passwordless_codes_pkey" PRIMARY KEY ("code_id")
);
-- CreateTable
CREATE TABLE "passwordless_devices" (
"device_id_hash" CHAR(44) NOT NULL,
"email" VARCHAR(256),
"phone_number" VARCHAR(256),
"link_code_salt" CHAR(44) NOT NULL,
"failed_attempts" INTEGER NOT NULL,
CONSTRAINT "passwordless_devices_pkey" PRIMARY KEY ("device_id_hash")
);
-- CreateTable
CREATE TABLE "passwordless_users" (
"user_id" CHAR(36) NOT NULL,
"email" VARCHAR(256),
"phone_number" VARCHAR(256),
"time_joined" BIGINT NOT NULL,
CONSTRAINT "passwordless_users_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "role_permissions" (
"role" VARCHAR(255) NOT NULL,
"permission" VARCHAR(255) NOT NULL,
CONSTRAINT "role_permissions_pkey" PRIMARY KEY ("role","permission")
);
-- CreateTable
CREATE TABLE "roles" (
"role" VARCHAR(255) NOT NULL,
CONSTRAINT "roles_pkey" PRIMARY KEY ("role")
);
-- CreateTable
CREATE TABLE "session_access_token_signing_keys" (
"created_at_time" BIGINT NOT NULL,
"value" TEXT,
CONSTRAINT "session_access_token_signing_keys_pkey" PRIMARY KEY ("created_at_time")
);
-- CreateTable
CREATE TABLE "session_info" (
"session_handle" VARCHAR(255) NOT NULL,
"user_id" VARCHAR(128) NOT NULL,
"refresh_token_hash_2" VARCHAR(128) NOT NULL,
"session_data" TEXT,
"expires_at" BIGINT NOT NULL,
"created_at_time" BIGINT NOT NULL,
"jwt_user_payload" TEXT,
CONSTRAINT "session_info_pkey" PRIMARY KEY ("session_handle")
);
-- CreateTable
CREATE TABLE "thirdparty_users" (
"third_party_id" VARCHAR(28) NOT NULL,
"third_party_user_id" VARCHAR(128) NOT NULL,
"user_id" CHAR(36) NOT NULL,
"email" VARCHAR(256) NOT NULL,
"time_joined" BIGINT NOT NULL,
CONSTRAINT "thirdparty_users_pkey" PRIMARY KEY ("third_party_id","third_party_user_id")
);
-- CreateTable
CREATE TABLE "user_metadata" (
"user_id" VARCHAR(128) NOT NULL,
"user_metadata" TEXT NOT NULL,
CONSTRAINT "user_metadata_pkey" PRIMARY KEY ("user_id")
);
-- CreateTable
CREATE TABLE "user_roles" (
"user_id" VARCHAR(128) NOT NULL,
"role" VARCHAR(255) NOT NULL,
CONSTRAINT "user_roles_pkey" PRIMARY KEY ("user_id","role")
);
-- CreateTable
CREATE TABLE "userid_mapping" (
"supertokens_user_id" CHAR(36) NOT NULL,
"external_user_id" VARCHAR(128) NOT NULL,
"external_user_id_info" TEXT,
CONSTRAINT "userid_mapping_pkey" PRIMARY KEY ("supertokens_user_id","external_user_id")
);
-- CreateIndex
CREATE INDEX "all_auth_recipe_users_pagination_index" ON "all_auth_recipe_users"("time_joined" DESC, "user_id" DESC);
-- CreateIndex
CREATE UNIQUE INDEX "emailpassword_pswd_reset_tokens_token_key" ON "emailpassword_pswd_reset_tokens"("token");
-- CreateIndex
CREATE INDEX "emailpassword_password_reset_token_expiry_index" ON "emailpassword_pswd_reset_tokens"("token_expiry");
-- CreateIndex
CREATE UNIQUE INDEX "emailpassword_users_email_key" ON "emailpassword_users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "emailverification_tokens_token_key" ON "emailverification_tokens"("token");
-- CreateIndex
CREATE INDEX "emailverification_tokens_index" ON "emailverification_tokens"("token_expiry");
-- CreateIndex
CREATE UNIQUE INDEX "passwordless_codes_link_code_hash_key" ON "passwordless_codes"("link_code_hash");
-- CreateIndex
CREATE INDEX "passwordless_codes_created_at_index" ON "passwordless_codes"("created_at");
-- CreateIndex
CREATE INDEX "passwordless_codes_device_id_hash_index" ON "passwordless_codes"("device_id_hash");
-- CreateIndex
CREATE INDEX "passwordless_devices_email_index" ON "passwordless_devices"("email");
-- CreateIndex
CREATE INDEX "passwordless_devices_phone_number_index" ON "passwordless_devices"("phone_number");
-- CreateIndex
CREATE UNIQUE INDEX "passwordless_users_email_key" ON "passwordless_users"("email");
-- CreateIndex
CREATE UNIQUE INDEX "passwordless_users_phone_number_key" ON "passwordless_users"("phone_number");
-- CreateIndex
CREATE INDEX "role_permissions_permission_index" ON "role_permissions"("permission");
-- CreateIndex
CREATE UNIQUE INDEX "thirdparty_users_user_id_key" ON "thirdparty_users"("user_id");
-- CreateIndex
CREATE INDEX "user_roles_role_index" ON "user_roles"("role");
-- CreateIndex
CREATE UNIQUE INDEX "userid_mapping_supertokens_user_id_key" ON "userid_mapping"("supertokens_user_id");
-- CreateIndex
CREATE UNIQUE INDEX "userid_mapping_external_user_id_key" ON "userid_mapping"("external_user_id");
-- AddForeignKey
ALTER TABLE "emailpassword_pswd_reset_tokens" ADD CONSTRAINT "emailpassword_pswd_reset_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "emailpassword_users"("user_id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "passwordless_codes" ADD CONSTRAINT "passwordless_codes_device_id_hash_fkey" FOREIGN KEY ("device_id_hash") REFERENCES "passwordless_devices"("device_id_hash") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "role_permissions" ADD CONSTRAINT "role_permissions_role_fkey" FOREIGN KEY ("role") REFERENCES "roles"("role") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "user_roles" ADD CONSTRAINT "user_roles_role_fkey" FOREIGN KEY ("role") REFERENCES "roles"("role") ON DELETE CASCADE ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "userid_mapping" ADD CONSTRAINT "userid_mapping_supertokens_user_id_fkey" FOREIGN KEY ("supertokens_user_id") REFERENCES "all_auth_recipe_users"("user_id") ON DELETE CASCADE ON UPDATE NO ACTION;

View File

@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (i.e. Git)
provider = "postgresql"

View File

@@ -0,0 +1,15 @@
import { INestApplication, Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close();
});
}
}

View File

@@ -0,0 +1,158 @@
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model all_auth_recipe_users {
user_id String @id @db.Char(36)
recipe_id String @db.VarChar(128)
time_joined BigInt
userid_mapping userid_mapping?
@@index([time_joined(sort: Desc), user_id(sort: Desc)], map: "all_auth_recipe_users_pagination_index")
}
model emailpassword_pswd_reset_tokens {
user_id String @db.Char(36)
token String @unique @db.VarChar(128)
token_expiry BigInt
emailpassword_users emailpassword_users @relation(fields: [user_id], references: [user_id], onDelete: Cascade)
@@id([user_id, token])
@@index([token_expiry], map: "emailpassword_password_reset_token_expiry_index")
}
model emailpassword_users {
user_id String @id @db.Char(36)
email String @unique @db.VarChar(256)
password_hash String @db.VarChar(128)
time_joined BigInt
emailpassword_pswd_reset_tokens emailpassword_pswd_reset_tokens[]
}
model emailverification_tokens {
user_id String @db.VarChar(128)
email String @db.VarChar(256)
token String @unique @db.VarChar(128)
token_expiry BigInt
@@id([user_id, email, token])
@@index([token_expiry], map: "emailverification_tokens_index")
}
model emailverification_verified_emails {
user_id String @db.VarChar(128)
email String @db.VarChar(256)
@@id([user_id, email])
}
model jwt_signing_keys {
key_id String @id @db.VarChar(255)
key_string String
algorithm String @db.VarChar(10)
created_at BigInt?
}
model key_value {
name String @id @db.VarChar(128)
value String?
created_at_time BigInt?
}
model passwordless_codes {
code_id String @id @db.Char(36)
device_id_hash String @db.Char(44)
link_code_hash String @unique @db.Char(44)
created_at BigInt
passwordless_devices passwordless_devices @relation(fields: [device_id_hash], references: [device_id_hash], onDelete: Cascade)
@@index([created_at], map: "passwordless_codes_created_at_index")
@@index([device_id_hash], map: "passwordless_codes_device_id_hash_index")
}
model passwordless_devices {
device_id_hash String @id @db.Char(44)
email String? @db.VarChar(256)
phone_number String? @db.VarChar(256)
link_code_salt String @db.Char(44)
failed_attempts Int
passwordless_codes passwordless_codes[]
@@index([email], map: "passwordless_devices_email_index")
@@index([phone_number], map: "passwordless_devices_phone_number_index")
}
model passwordless_users {
user_id String @id @db.Char(36)
email String? @unique @db.VarChar(256)
phone_number String? @unique @db.VarChar(256)
time_joined BigInt
}
model role_permissions {
role String @db.VarChar(255)
permission String @db.VarChar(255)
roles roles @relation(fields: [role], references: [role], onDelete: Cascade, onUpdate: NoAction)
@@id([role, permission])
@@index([permission], map: "role_permissions_permission_index")
}
model roles {
role String @id @db.VarChar(255)
role_permissions role_permissions[]
user_roles user_roles[]
}
model session_access_token_signing_keys {
created_at_time BigInt @id
value String?
}
model session_info {
session_handle String @id @db.VarChar(255)
user_id String @db.VarChar(128)
refresh_token_hash_2 String @db.VarChar(128)
session_data String?
expires_at BigInt
created_at_time BigInt
jwt_user_payload String?
}
model thirdparty_users {
third_party_id String @db.VarChar(28)
third_party_user_id String @db.VarChar(128)
user_id String @unique @db.Char(36)
email String @db.VarChar(256)
time_joined BigInt
@@id([third_party_id, third_party_user_id])
}
model user_metadata {
user_id String @id @db.VarChar(128)
user_metadata String
}
model user_roles {
user_id String @db.VarChar(128)
role String @db.VarChar(255)
roles roles @relation(fields: [role], references: [role], onDelete: Cascade, onUpdate: NoAction)
@@id([user_id, role])
@@index([role], map: "user_roles_role_index")
}
model userid_mapping {
supertokens_user_id String @unique @db.Char(36)
external_user_id String @unique @db.VarChar(128)
external_user_id_info String?
all_auth_recipe_users all_auth_recipe_users @relation(fields: [supertokens_user_id], references: [user_id], onDelete: Cascade, onUpdate: NoAction)
@@id([supertokens_user_id, external_user_id])
}

View File

@@ -0,0 +1,19 @@
import { Controller, Get, Session, UseGuards } from '@nestjs/common';
import { AuthGuard } from './auth/guards/auth.guard';
import { SessionContainer } from "supertokens-node/recipe/session";
@Controller()
export class AppController {
@Get()
getHello(): string {
return "API";
}
@Get('test')
@UseGuards(AuthGuard)
async getTest(@Session() session: SessionContainer): Promise<string> {
// TODO: magic
return "magic";
}
}

37
backend/src/app.module.ts Normal file
View File

@@ -0,0 +1,37 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AuthModule } from './auth/auth.module';
import { ConfigModule } from '@nestjs/config';
import { GraphQLISODateTime, GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { ApolloServerPluginLandingPageLocalDefault } from 'apollo-server-core';
// import { UsersModule } from './users/users.module';
@Module({
imports: [
ConfigModule.forRoot(),
AuthModule.forRoot({
connectionURI: process.env.ST_API_URL,
apiKey: process.env.ST_API_KEY,
appInfo: {
appName: process.env.APP_NAME,
apiDomain: process.env.APP_URL,
websiteDomain: process.env.WEBAPP_URL,
apiBasePath: '/auth/api',
websiteBasePath: '/auth',
},
}),
GraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
typePaths: ['./**/*.graphql'],
debug: true,
playground: false,
resolvers: { DateTime: GraphQLISODateTime },
plugins: [ApolloServerPluginLandingPageLocalDefault()],
}),
// UsersModule,
],
controllers: [AppController],
providers: [],
})
export class AppModule {}

View File

@@ -0,0 +1,43 @@
import {
DynamicModule,
MiddlewareConsumer,
Module,
NestModule,
} from '@nestjs/common';
import { AuthMiddleware } from './middlewares/auth.middleware';
import { AuthModuleConfig, ConfigInjectionToken } from './interfaces/config.interface';
import { SupertokensService } from './supertokens/supertokens.service';
@Module({
providers: [],
exports: [],
controllers: [],
})
export class AuthModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(AuthMiddleware).forRoutes('*');
}
static forRoot({
connectionURI,
apiKey,
appInfo,
}: AuthModuleConfig): DynamicModule {
return {
providers: [
{
useValue: {
appInfo,
connectionURI,
apiKey,
},
provide: ConfigInjectionToken,
},
SupertokensService,
],
exports: [],
imports: [],
module: AuthModule,
};
}
}

View File

@@ -0,0 +1,8 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const Session = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.session;
},
);

View File

@@ -0,0 +1,29 @@
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common';
import { errorHandler } from 'supertokens-node/framework/express';
import { Error as STError } from 'supertokens-node';
import { Request, Response, NextFunction, ErrorRequestHandler } from 'express';
@Catch(STError)
export class SupertokensExceptionFilter implements ExceptionFilter {
handler: ErrorRequestHandler;
constructor() {
this.handler = errorHandler();
}
catch(exception: Error, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const resp = ctx.getResponse<Response>();
if (resp.headersSent) {
return;
}
this.handler(
exception,
ctx.getRequest<Request>(),
resp,
ctx.getNext<NextFunction>(),
);
}
}

View File

@@ -0,0 +1,31 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Error as STError } from 'supertokens-node';
import { verifySession } from 'supertokens-node/recipe/session/framework/express';
@Injectable()
export class AuthGuard implements CanActivate {
async canActivate(context: ExecutionContext): Promise<boolean> {
const ctx = context.switchToHttp();
let err = undefined;
const resp = ctx.getResponse();
// You can create an optional version of this by passing {sessionRequired: false} to verifySession
await verifySession()(ctx.getRequest(), resp, (res) => {
err = res;
});
if (resp.headersSent) {
throw new STError({
message: 'RESPONSE_SENT',
type: 'RESPONSE_SENT',
});
}
if (err) {
throw err;
}
return true;
}
}

View File

@@ -0,0 +1,9 @@
import { AppInfo } from 'supertokens-node/types';
export const ConfigInjectionToken = 'ConfigInjectionToken';
export type AuthModuleConfig = {
appInfo: AppInfo;
connectionURI: string;
apiKey?: string;
};

View File

@@ -0,0 +1,15 @@
import { Injectable, NestMiddleware } from '@nestjs/common';
import { middleware } from 'supertokens-node/framework/express';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
supertokensMiddleware: any;
constructor() {
this.supertokensMiddleware = middleware();
}
use(req: Request, res: any, next: () => void) {
return this.supertokensMiddleware(req, res, next);
}
}

View File

@@ -0,0 +1,78 @@
import { Inject, Injectable } from '@nestjs/common';
import {
AuthModuleConfig,
ConfigInjectionToken,
} from '../interfaces/config.interface';
import supertokens from 'supertokens-node';
import ThirdPartyEmailPassword from 'supertokens-node/recipe/thirdpartyemailpassword';
import EmailPassword from 'supertokens-node/recipe/emailpassword';
import { STMPService } from 'supertokens-node/recipe/thirdpartyemailpassword/emaildelivery';
import Session from 'supertokens-node/recipe/session';
@Injectable()
export class SupertokensService {
constructor(@Inject(ConfigInjectionToken) private config: AuthModuleConfig) {
supertokens.init({
appInfo: config.appInfo,
supertokens: {
connectionURI: config.connectionURI,
apiKey: config.apiKey,
},
recipeList: [
EmailPassword.init({
emailDelivery: {
service: new STMPService({
smtpSettings: {
host: process.env.SMTP_HOST,
authUsername: process.env.SMTP_USERNAME,
password: process.env.SMTP_PASSWORD,
port: parseInt(process.env.SMTP_PORT),
from: {
name: process.env.SMTP_FROM_NAME,
email: process.env.SMTP_FROM_EMAIL,
},
secure: process.env.SMTP_SECURE ? true : false,
},
}),
},
}),
ThirdPartyEmailPassword.init({
providers: [
ThirdPartyEmailPassword.Google({
clientId:
'1060725074195-kmeum4crr01uirfl2op9kd5acmi9jutn.apps.googleusercontent.com',
clientSecret: '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',
}),
],
}),
Session.init({
// antiCsrf: "VIA_CUSTOM_HEADER",
jwt: {
enable: true,
issuer: `${process.env.APP_URL}/auth/api`,
},
override: {
functions: function (originalImplementation) {
return {
...originalImplementation,
createNewSession: async function (input) {
input.accessTokenPayload = {
...input.accessTokenPayload,
'https://hasura.io/jwt/claims': {
'x-hasura-user-id': input.userId,
'x-hasura-default-role': 'user',
'x-hasura-allowed-roles': ['user'],
},
};
return originalImplementation.createNewSession(input);
},
};
},
},
}),
],
});
}
}

View File

@@ -0,0 +1,48 @@
/*
* -------------------------------------------------------
* THIS FILE WAS AUTOMATICALLY GENERATED (DO NOT MODIFY)
* -------------------------------------------------------
*/
/* tslint:disable */
/* eslint-disable */
export class CreateUserInput {
email: string;
password: string;
}
export class UpdateUserInput {
email?: Nullable<string>;
password?: Nullable<string>;
time_joined?: Nullable<number>;
createdAt?: Nullable<DateTime>;
updatedAt?: Nullable<DateTime>;
}
export class User {
id: string;
email: string;
password?: Nullable<string>;
time_joined?: Nullable<number>;
createdAt?: Nullable<DateTime>;
updatedAt?: Nullable<DateTime>;
}
export abstract class IQuery {
abstract users(): Nullable<User>[] | Promise<Nullable<User>[]>;
abstract user(id: string): Nullable<User> | Promise<Nullable<User>>;
}
export abstract class IMutation {
abstract createUser(createUserInput: CreateUserInput): User | Promise<User>;
abstract updateUser(id: string, updateUserInput: UpdateUserInput): Nullable<User> | Promise<Nullable<User>>;
abstract removeUser(id: string): Nullable<User> | Promise<Nullable<User>>;
}
export type DateTime = any;
type Nullable<T> = T | null;

20
backend/src/main.ts Normal file
View File

@@ -0,0 +1,20 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import supertokens from 'supertokens-node';
import { SupertokensExceptionFilter } from './auth/filters/auth.filter';
// import { PrismaService } from 'prisma/prisma.service';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors({
origin: [process.env.WEBAPP_URL, 'http://server.home:8081'],
allowedHeaders: ['content-type', ...supertokens.getAllCORSHeaders()],
credentials: true,
});
app.useGlobalFilters(new SupertokensExceptionFilter());
// const prismaService = app.get(PrismaService);
// await prismaService.enableShutdownHooks(app);
await app.listen(process.env.APP_PORT);
}
bootstrap();

View File

@@ -0,0 +1,34 @@
scalar DateTime
type User {
id: ID!
email: String!
password: String
time_joined: Int
createdAt: DateTime
updatedAt: DateTime
}
input CreateUserInput {
email: String!
password: String!
}
input UpdateUserInput {
email: String
password: String
time_joined: Int
createdAt: DateTime
updatedAt: DateTime
}
type Query {
users: [User]!
user(id: ID!): User
}
type Mutation {
createUser(createUserInput: CreateUserInput!): User!
updateUser(id: ID!, updateUserInput: UpdateUserInput!): User
removeUser(id: ID!): User
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersResolver } from './users.resolver';
import { PrismaService } from 'prisma/prisma.service';
@Module({
providers: [PrismaService, UsersResolver, UsersService],
})
export class UsersModule {}

View File

@@ -0,0 +1,32 @@
import { Resolver, Query, Mutation, Args } from '@nestjs/graphql';
import { Prisma } from '@prisma/client';
import { UsersService } from './users.service';
@Resolver('User')
export class UsersResolver {
constructor(private readonly usersService: UsersService) {}
// @Mutation('createUser')
// create(@Args('createUserInput') createUserInput: Prisma.UserCreateInput) {
// return this.usersService.create(createUserInput);
// }
// @Query('users')
// findAll(@Args('params') params?: Prisma.UserFindManyArgs) {
// return this.usersService.users(params);
// }
// @Query('user')
// findOne(@Args('id') id: string) {
// return this.usersService.user({ id });
// }
// @Mutation('updateUser')
// update(@Args('updateUserInput') updateUserInput: UpdateUserInput) {
// return this.usersService.update(updateUserInput.id, updateUserInput);
// }
// @Mutation('removeUser')
// remove(@Args('id') id: string) {
// return this.usersService.remove(id);
// }
}

View File

@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
// import { Prisma, User } from '@prisma/client';
import { PrismaService } from 'prisma/prisma.service';
@Injectable()
export class UsersService {
constructor(private readonly prismaService: PrismaService) {}
// async user(uniqueInput: Prisma.UserWhereUniqueInput) {
// return await this.prismaService.user.findUnique({ where: uniqueInput });
// }
// async users(params?: Prisma.UserFindManyArgs) {
// return await this.prismaService.user.findMany(params);
// }
// create(createUserInput: Prisma.UserCreateInput) {
// return this.prismaService.user.create({ data: createUserInput });
// }
// update(id: string, userUpdateInput: Prisma.UserUpdateInput) {
// return this.prismaService.user.update({
// where: { id },
// data: userUpdateInput,
// });
// }
// remove(id: string) {
// return `This action removes a #${id} user`;
// }
}

View File

@@ -0,0 +1,24 @@
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';
describe('AppController (e2e)', () => {
let app: INestApplication;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [AppModule],
}).compile();
app = moduleFixture.createNestApplication();
await app.init();
});
it('/ (GET)', () => {
return request(app.getHttpServer())
.get('/')
.expect(200)
.expect('Hello World!');
});
});

View File

@@ -0,0 +1,9 @@
{
"moduleFileExtensions": ["js", "json", "ts"],
"rootDir": ".",
"testEnvironment": "node",
"testRegex": ".e2e-spec.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
}

View File

@@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
backend/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2017",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}

5261
backend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -28,6 +28,6 @@
"@urql/core": "^3.0.3",
"graphql": "^16.6.0",
"solid-js": "^1.5.1",
"solid-urql": "^0.2.0"
"supertokens-web-js": "^0.1.6"
}
}

26
frontend/src/App.tsx Normal file
View File

@@ -0,0 +1,26 @@
import type { Component } from "solid-js";
import AppRouter from "./routes";
import SuperTokens from "supertokens-web-js";
import EmailPass from "supertokens-web-js/recipe/emailpassword";
import Session from "supertokens-web-js/recipe/session";
import AuthProvider from "./context/AuthContext";
SuperTokens.init({
appInfo: {
apiDomain: "http://localhost:3300",
apiBasePath: "/auth/api",
appName: "Fluxem",
},
recipeList: [EmailPass.init(), Session.init()],
});
const App: Component = () => {
return (
<AuthProvider>
<AppRouter />
</AuthProvider>
);
};
export default App;

View File

@@ -0,0 +1,9 @@
const AuthLoader = () => {
return (
<div>
<h2>Auth Loading...</h2>
</div>
)
}
export default AuthLoader;

View File

@@ -0,0 +1,13 @@
interface TestComponentProps {
// add props here
}
function TestComponent(props: TestComponentProps) {
return (
<div>
<h2>TestComponent</h2>
</div>
)
}
export default TestComponent;

View File

@@ -0,0 +1,69 @@
import { useNavigate } from "@solidjs/router";
import { createContext, onMount, Show, useContext } from "solid-js";
import { createStore } from "solid-js/store";
import { UserType } from "supertokens-web-js/recipe/emailpassword";
import AuthLoader from "../components/AuthLoader";
import { currentUser } from "../services/auth.service";
const AuthStateContext = createContext();
const AuthDispatchContext = createContext<any>();
interface InitState {
isLoading: boolean;
isAuthenticated: boolean;
currentUser: UserType | null;
}
const initialState: InitState = {
isLoading: true,
isAuthenticated: false,
currentUser: null,
};
const AuthProvider = (props: any) => {
const [store, setStore] = createStore(initialState);
const navigate = useNavigate();
const loadCurrentUser = async () => {
const user = await currentUser();
if (user) {
setStore("isAuthenticated", true);
setStore("currentUser", user);
}
};
onMount(async () => {
await loadCurrentUser();
setStore("isLoading", false);
});
const setCurrentUser = (user?: UserType) => {
if (user) {
setStore("isAuthenticated", true);
setStore("currentUser", user);
}
};
const removeCurrentUser = () => {
setStore("isAuthenticated", false);
setStore("currentUser", null);
};
return (
<AuthStateContext.Provider value={store}>
<AuthDispatchContext.Provider
value={{
setCurrentUser,
removeCurrentUser,
}}
>
<Show when={!store.isLoading} fallback={<AuthLoader />}>
{props.children}
</Show>
</AuthDispatchContext.Provider>
</AuthStateContext.Provider>
);
};
export default AuthProvider;
export const useAuthState = () => useContext(AuthStateContext);
export const useAuthDispatch = () => useContext(AuthDispatchContext);

View File

@@ -0,0 +1,5 @@
import { createClient } from "@urql/core";
const client = createClient({
url: ''
});

View File

@@ -0,0 +1,61 @@
import { useNavigate } from "@solidjs/router";
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";
import { useAuthDispatch } from "../../context/AuthContext";
import {
delUserFromLocalStorage,
loginService,
logoutService,
setUserInLocalStorage,
} from "../../services/auth.service";
const useLogin = () => {
const [loading, setLoading] = createSignal(false);
const [form, setForm] = createStore({
email: "",
password: "",
});
const { setCurrentUser } = useAuthDispatch();
const navigate = useNavigate();
const handleInput = (ev: any) => {
setForm([ev.currentTarget.name], ev.currentTarget.value);
};
const handleLogin = async (ev: any) => {
ev.preventDefault();
setLoading(true);
try {
const loginData = await loginService({
formFields: [
{ id: "email", value: form.email },
{ id: "password", value: form.password },
],
});
if (loginData.status === "OK") {
setCurrentUser(loginData.user);
setUserInLocalStorage(loginData.user);
navigate("/", { replace: true });
}
if (loginData.status === "FIELD_ERROR") {
// TODO: Handle error in UI
console.log("FIELD_ERROR", loginData);
}
if (loginData.status === "WRONG_CREDENTIALS_ERROR") {
// TODO: Handle error in UI
console.log("WRONG_CREDENTIALS_ERROR", loginData);
}
} catch (error) {
//TODO: Handle error in UI
console.error("ERRRRRRRRRRr", error);
} finally {
setLoading(false);
}
};
return { handleInput, loading, handleLogin, form };
};
export default useLogin;

View File

@@ -0,0 +1,28 @@
import { createSignal } from "solid-js";
import { useAuthDispatch } from "../../context/AuthContext";
import {
delUserFromLocalStorage,
logoutService,
} from "../../services/auth.service";
const useLogout = () => {
const [loading, setLoading] = createSignal(false);
const { removeCurrentUser } = useAuthDispatch();
const handleLogout = async () => {
try {
setLoading(true);
await logoutService();
} catch (error) {
console.log(error);
} finally {
removeCurrentUser();
delUserFromLocalStorage();
setLoading(false);
}
};
return { handleLogout, loading };
};
export default useLogout;

View File

@@ -0,0 +1,34 @@
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";
import EmailPassRecipe from "supertokens-web-js/recipe/emailpassword";
const useRegister = () => {
const [loading, setLoading] = createSignal(false);
const [form, setForm] = createStore({
email: "",
password: "",
});
const handleInput = (ev: any) => {
setForm([ev.currentTarget.name], ev.currentTarget.value);
};
const handleRegister = async (ev: any) => {
ev.preventDefault();
setLoading(true);
try {
const login = await EmailPassRecipe.signUp({
formFields: [
{ id: "email", value: form.email },
{ id: "password", value: form.password },
],
});
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
return { handleInput, loading, handleRegister, form };
};
export default useRegister;

View File

@@ -4,7 +4,6 @@ import { render } from "solid-js/web";
import "./index.css";
import App from "./App";
import { Router } from "@solidjs/router";
render(
() => (
<Router>

View File

@@ -5,7 +5,7 @@ const Home = lazy(() => import("./views/home/Home"));
const AuthLayout = lazy(() => import("./views/auth/AuthLayout"));
const Login = lazy(() => import("./views/auth/Login"));
const Register = lazy(() => import("./views/auth/Register"));
const VerifyEmail = lazy(() => import("./views/auth/VerifyEmail"));
const AppRouter: Component = () => {
return (
<Routes>
@@ -13,6 +13,7 @@ const AppRouter: Component = () => {
<Route path="/auth" element={<AuthLayout />}>
<Route path="/login" element={<Login />}></Route>
<Route path="/register" element={<Register />}></Route>
<Route path="/verify-email" element={<VerifyEmail />}></Route>
</Route>
</Routes>
);

View File

@@ -0,0 +1,52 @@
import { useNavigate } from "@solidjs/router";
import { Component, Show } from "solid-js";
import { useAuthState } from "../../../context/AuthContext";
import useLogin from "../../../hooks/auth/login.hook";
const Login: Component = () => {
const navigate = useNavigate();
const { handleLogin, handleInput, loading, form } = useLogin();
const authState: any = useAuthState();
if (authState.isAuthenticated) {
navigate("/");
} else {
return (
<div>
<form onsubmit={handleLogin}>
<div>
<label for="email">Username or Email</label>
<br />
<input
id="email"
type="email"
name="email"
placeholder="Email/Username"
value={form.email}
onInput={handleInput}
required
/>
</div>
<div>
<label for="password">Password</label>
<br />
<input
id="password"
type="password"
name="password"
placeholder="Password"
value={form.password}
onInput={handleInput}
required
/>
</div>
<br />
<button disabled={loading()} type="submit">
<Show when={!loading()} fallback="Logging in...">
Log In
</Show>
</button>
</form>
</div>
);
}
};
export default Login;

View File

@@ -1,11 +1,10 @@
import type { Component } from "solid-js";
const Home: Component = () => {
const Register: Component = () => {
return (
<div>
<h2>Home</h2>
<h2>Register</h2>
</div>
)
}
export default Home;
export default Register;

View File

@@ -0,0 +1,18 @@
import EmailPassRecipe from "supertokens-web-js/recipe/emailpassword";
const VerifyEmail = () => {
let text = "Verifying email";
const verifyEmail = EmailPassRecipe.verifyEmail();
verifyEmail.then((data) => {
console.log(data);
text = "Email verified successfully";
});
// console.log(verifyEmail);
return (
<div>
<h2>{text}</h2>
</div>
);
};
export default VerifyEmail;

View File

@@ -0,0 +1,31 @@
import { Component, Show } from "solid-js";
import { useAuthState } from "../../../context/AuthContext";
import useLogin from "../../../hooks/auth/login.hook";
import useLogout from "../../../hooks/auth/logout.hook";
const Home: Component = () => {
const authState: any = useAuthState();
const { handleLogout, loading } = useLogout();
return (
<div>
<p>Home</p>
<Show when={authState?.isAuthenticated}>
<div>
<p>Authenticated</p>
<button onclick={handleLogout} disabled={loading()}>
<Show when={!loading()} fallback="Signing out...">
Sign Out
</Show>
</button>
<p>{JSON.stringify(authState)}</p>
</div>
</Show>
<Show when={!authState?.isAuthenticated}>
<a href="/auth/login">Login</a>
</Show>
</div>
);
};
export default Home;

View File

@@ -0,0 +1,61 @@
import EmailPassRecipe, {
UserType,
} from "supertokens-web-js/recipe/emailpassword";
import Session from "supertokens-web-js/recipe/session";
const curentUserKey = "current_user";
export interface AuthData {
formFields: FormFields[];
}
interface FormFields {
id: string;
value: string;
}
const currentUser = async () => {
// This method might produce bugs. Localstorage may not be the same as logged in user
// TODO: endpoing in backend (getCurrentUser)
if (
localStorage.getItem("current_user") &&
(await Session.doesSessionExist())
) {
let sessionUserId = await Session.getUserId();
const user = getUserFromLocaStorage();
if (user) {
if (sessionUserId === user.id) {
return user;
}
}
}
return null;
};
const loginService = async (loginData: AuthData) => {
return await EmailPassRecipe.signIn(loginData);
};
const logoutService = async () => {
await EmailPassRecipe.signOut();
};
const setUserInLocalStorage = (user: UserType) => {
localStorage.setItem(curentUserKey, JSON.stringify(user));
};
const getUserFromLocaStorage = () => {
const user: UserType = JSON.parse(localStorage.getItem(curentUserKey)!);
return user;
};
const delUserFromLocalStorage = () => {
localStorage.removeItem(curentUserKey);
};
export {
loginService,
logoutService,
currentUser,
setUserInLocalStorage,
delUserFromLocalStorage,
};

View File

@@ -1297,6 +1297,13 @@ braces@^3.0.2, braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
browser-tabs-lock@^1.2.14:
version "1.2.15"
resolved "https://registry.yarnpkg.com/browser-tabs-lock/-/browser-tabs-lock-1.2.15.tgz#d5012e652e2a0cb4eba471b0a2300c2fa5d92788"
integrity sha512-J8K9vdivK0Di+b8SBdE7EZxDr88TnATing7XoLw6+nFkXMQ6sVBh92K3NQvZlZU91AIkFRi0w3sztk5Z+vsswA==
dependencies:
lodash ">=4.17.21"
browserslist@^4.20.2:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
@@ -2465,7 +2472,7 @@ lodash.once@^4.0.0:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==
lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0:
lodash@>=4.17.21, lodash@^4.17.20, lodash@^4.17.21, lodash@~4.17.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@@ -3084,11 +3091,6 @@ solid-refresh@^0.4.1:
"@babel/helper-module-imports" "^7.16.7"
"@babel/types" "^7.18.4"
solid-urql@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/solid-urql/-/solid-urql-0.2.0.tgz#b6305480a4e46d176a195a4bc586291e19944a7a"
integrity sha512-3sLtHBbbfwANBTP0toZLdVeRKa4UJDBjgfP5rv2z4ip4+a8vBlfFHWy/5N6fgxBhQ0IdbuYKRAcJECkJd80auw==
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@@ -3134,6 +3136,27 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1:
dependencies:
ansi-regex "^5.0.1"
supertokens-js-override@0.0.4, supertokens-js-override@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/supertokens-js-override/-/supertokens-js-override-0.0.4.tgz#9af583fbc5e1f0195dbb358c4fcf75f44c76dc09"
integrity sha512-r0JFBjkMIdep3Lbk3JA+MpnpuOtw4RSyrlRAbrzMcxwiYco3GFWl/daimQZ5b1forOiUODpOlXbSOljP/oyurg==
supertokens-web-js@^0.1.6:
version "0.1.6"
resolved "https://registry.yarnpkg.com/supertokens-web-js/-/supertokens-web-js-0.1.6.tgz#81165f9f7604518db05088a8a527c756b3b48178"
integrity sha512-Cyu97r6tRJc4ryiKhOqYlqYQ+1XB5x5rJfMudg0kgANu7bvNMo39mI4KAb4eLaXQfXAPQ6Y/As+uqUVP7gbKDw==
dependencies:
supertokens-js-override "0.0.4"
supertokens-website "^13.0.2"
supertokens-website@^13.0.2:
version "13.0.2"
resolved "https://registry.yarnpkg.com/supertokens-website/-/supertokens-website-13.0.2.tgz#c1eba0d1745ee2d7c019fa88eb9d7029309fc6e9"
integrity sha512-WVCHky05ndJhPXW2khAWy3CBlWn1ZwRF32TPTiGgYLrpQYO9q5doHJi9z2H1OiSmOEFJDEKWtaPW9HxAHABo1Q==
dependencies:
browser-tabs-lock "^1.2.14"
supertokens-js-override "^0.0.4"
supports-color@^5.3.0:
version "5.5.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"

View File

@@ -1,14 +0,0 @@
import type { Component } from "solid-js";
import { Provider } from "solid-urql";
import client from "./graphql/client";
import AppRouter from "./routes";
const App: Component = () => {
return (
<Provider value={client}>
<AppRouter />
</Provider>
);
};
export default App;

View File

@@ -1,7 +0,0 @@
import { createClient } from "solid-urql";
const client = createClient({
url: 'https://hasura.apps.artservis.al/v1/graphql'
});
export default client;

View File

@@ -1,22 +0,0 @@
import { createSignal } from "solid-js";
import { createStore } from "solid-js/store";
const useLogin = () => {
const [loading, setLoading] = createSignal(false);
const [form, setForm] = createStore({
username: "",
password: "",
});
const handleInput = (ev: any) => {
setForm([ev.currentTarget.name], ev.currentTarget.value);
};
const handleLogin = (ev: any) => {
ev.preventDefault();
setLoading(true);
};
return { handleInput, loading, handleLogin, form };
};
export default useLogin;

View File

@@ -1,39 +0,0 @@
import { Component, Show } from "solid-js";
import useLogin from "../../../hooks/auth/login.hook";
const { handleLogin, handleInput, loading, form } = useLogin();
const Login: Component = () => {
return (
<div>
<form onsubmit={handleLogin}>
<div>
{/* <label for="name">Username or Email</label> */}
<input
type="text"
name="username"
placeholder="Email/Username"
value={form.username}
onInput={handleInput}
/>
</div>
<div>
{/* <label for="name">Password</label> */}
<input
type="password"
name="password"
placeholder="Password"
value={form.password}
onInput={handleInput}
/>
</div>
<button disabled={loading()} type="submit">
<Show when={!loading()} fallback="Logging in...">
Log In
</Show>
</button>
</form>
</div>
);
};
export default Login;

View File

@@ -1,20 +0,0 @@
import type { Component } from "solid-js";
import { useAllUsersQuery } from "../../../graphql/generated/graphql";
const mockHasura = () => {
const getUsers = useAllUsersQuery();
console.log(getUsers);
};
const Register: Component = () => {
return (
<div>
<h2>Register</h2>
<button onclick={mockHasura}>Mock</button>
</div>
);
};
export default Register;