├── BUILD
├── npm
├── BUILD
├── ReadMe.md
├── package.json
└── yarn.lock
├── frontend
├── BUILD
├── .gitignore
├── app
│ ├── .gitignore
│ ├── .babelrc
│ ├── assets
│ │ └── frappe.png
│ ├── src
│ │ ├── util
│ │ │ └── url.js
│ │ ├── index.html
│ │ ├── index.js
│ │ ├── protoHelper.js
│ │ ├── components
│ │ │ └── Protected.js
│ │ ├── HelloWorld.js
│ │ └── hooks
│ │ │ └── useAuthData.js
│ ├── entry.sh
│ ├── nginx.conf
│ ├── nginxDev.conf
│ ├── package.json
│ ├── webpack.config.js
│ ├── webpack.dev.js
│ ├── webpack.prod.js
│ └── BUILD
├── next_app
│ ├── public
│ │ ├── favicon.ico
│ │ └── vercel.svg
│ ├── pages
│ │ ├── api
│ │ │ ├── hello.js
│ │ │ └── auth
│ │ │ │ └── [...nextauth].js
│ │ ├── _app.js
│ │ └── index.js
│ ├── postcss.config.js
│ ├── next.config.js
│ ├── util
│ │ ├── protoHelper.js
│ │ └── ReadMe.md
│ ├── tailwind.config.js
│ ├── .gitignore
│ ├── entry.sh
│ ├── package.json
│ ├── components
│ │ └── Login.js
│ ├── README.md
│ └── BUILD
├── envoy
│ ├── BUILD
│ └── envoy.yaml
└── ReadMe.md
├── nodejs
├── .bazelversion
├── .gitignore
├── .bazelignore
├── ReadMe.md
├── package.json
├── BUILD
├── gateway.js
└── .bazelrc
├── local
├── ReadMe.md
├── init.js
└── BUILD
├── java
├── README.md
├── com
│ ├── user
│ │ ├── util
│ │ │ ├── SSOValidator.java
│ │ │ ├── FakeSSOValidator.java
│ │ │ ├── BUILD
│ │ │ ├── MainSSOValidator.java
│ │ │ └── JWTUtil.java
│ │ ├── db
│ │ │ ├── UserDBHandler.java
│ │ │ ├── BUILD
│ │ │ ├── FakeUserDBHandler.java
│ │ │ └── MainUserDBHandler.java
│ │ └── management
│ │ │ ├── UserManagementService.java
│ │ │ ├── BUILD
│ │ │ └── UserManagementServiceImpl.java
│ ├── task
│ │ ├── README.md
│ │ ├── TaskService.java
│ │ ├── Constants.java
│ │ ├── TaskServiceImpl.java
│ │ ├── BUILD
│ │ └── AuthenticatedInterceptor.java
│ └── util
│ │ ├── ServiceModule.java
│ │ ├── FakeServiceModule.java
│ │ ├── SetupUtil.java
│ │ └── BUILD
└── test
│ ├── java_testing_generator.bzl
│ └── com
│ ├── task
│ ├── BUILD
│ └── TaskServiceTest.java
│ └── user
│ └── management
│ ├── BUILD
│ └── UserManagementServiceTest.java
├── spec_local
├── ns_slot.yaml
├── service_task.yaml
├── service_mongodb.yaml
├── service_envoy.yaml
├── service_frapp.yaml
├── service_user_management.yaml
├── service_gateway.yaml
├── service_next_frapp.yaml
├── ReadMe.md
├── deployment_mongodb.yaml
├── deployment_task.yaml
├── deployment_envoy.yaml
├── deployment_frapp.yaml
├── deployment_gateway.yaml
├── deployment_user_management.yaml
├── deployment_next_frapp.yaml
├── ingress.yaml
├── startup.sh
└── BUILD
├── monorepo-diagram.png
├── README.md
├── proto
├── task
│ ├── task_service.proto
│ └── BUILD
└── user
│ ├── user.proto
│ ├── BUILD
│ ├── user_management_service.proto
│ └── db.proto
├── .bazelrc
├── .gitignore
└── WORKSPACE
/BUILD:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/npm/BUILD:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/BUILD:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frontend/.gitignore:
--------------------------------------------------------------------------------
1 | genProto/**
--------------------------------------------------------------------------------
/nodejs/.bazelversion:
--------------------------------------------------------------------------------
1 | 3.0.0
2 |
3 |
--------------------------------------------------------------------------------
/local/ReadMe.md:
--------------------------------------------------------------------------------
1 | Local specific db for testing
2 |
--------------------------------------------------------------------------------
/nodejs/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | dist
3 | bazel-out
4 | node_modules
5 |
--------------------------------------------------------------------------------
/nodejs/.bazelignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | bazel-out
4 |
5 |
--------------------------------------------------------------------------------
/frontend/app/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | devDist/
3 |
4 | src/genProto/**
5 |
6 | .env
--------------------------------------------------------------------------------
/java/README.md:
--------------------------------------------------------------------------------
1 | # Backend
2 |
3 | gRPC servers mainly written in Java
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/app/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["@babel/preset-env", "@babel/preset-react"]
3 | }
--------------------------------------------------------------------------------
/spec_local/ns_slot.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Namespace
3 | metadata:
4 | name: slot
5 |
--------------------------------------------------------------------------------
/monorepo-diagram.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/monorepo-diagram.png
--------------------------------------------------------------------------------
/frontend/app/assets/frappe.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/frontend/app/assets/frappe.png
--------------------------------------------------------------------------------
/frontend/next_app/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kunal-rp/monorepo_template/HEAD/frontend/next_app/public/favicon.ico
--------------------------------------------------------------------------------
/frontend/app/src/util/url.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | module.exports = {
4 | gatewayUrl: () => process.env.LOCAL_URL != undefined ? process.env.LOCAL_URL : "PH_REACT_APP_BASE_URL"
5 | }
--------------------------------------------------------------------------------
/frontend/app/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Slot Frapp
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/nodejs/ReadMe.md:
--------------------------------------------------------------------------------
1 | ##Example nodejs rest gateway for grpc services
2 |
3 | to create image, will need to specify platofrm : bazel run --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 TARGET
4 |
--------------------------------------------------------------------------------
/npm/ReadMe.md:
--------------------------------------------------------------------------------
1 | This npm repo is used for hardcoded tools in grpc proto libraries
2 |
3 | https://github.com/rules-proto-grpc/rules_proto_grpc/blob/caa83f1d78b1b7ac3b891887771c0de7330503a1/js/BUILD.bazel
4 |
5 |
--------------------------------------------------------------------------------
/frontend/next_app/pages/api/hello.js:
--------------------------------------------------------------------------------
1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction
2 |
3 | export default function helloAPI(req, res) {
4 | res.status(200).json({ name: 'John Doe' })
5 | }
6 |
--------------------------------------------------------------------------------
/java/com/user/util/SSOValidator.java:
--------------------------------------------------------------------------------
1 | package com.user.util;
2 |
3 | import java.util.Optional;
4 | import com.user.UserProto.User;
5 |
6 | public interface SSOValidator {
7 |
8 | public Optional validateGoogleIdToken(String idToken);
9 | }
--------------------------------------------------------------------------------
/spec_local/service_task.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: task
5 | namespace: slot
6 | spec:
7 | selector:
8 | app: task
9 | ports:
10 | - protocol: "TCP"
11 | port: 80
12 | targetPort: 80
--------------------------------------------------------------------------------
/java/test/java_testing_generator.bzl:
--------------------------------------------------------------------------------
1 | def java_unit_test(name, pkg,srcs, deps):
2 | for src in srcs:
3 | src_name = src[:-5]
4 | native.java_test(name=src_name , test_class=pkg+'.'+src_name , srcs=srcs, deps=deps, size="small")
5 |
6 |
7 |
--------------------------------------------------------------------------------
/frontend/next_app/postcss.config.js:
--------------------------------------------------------------------------------
1 | // If you want to use other PostCSS plugins, see the following:
2 | // https://tailwindcss.com/docs/using-with-preprocessors
3 | module.exports = {
4 | plugins: {
5 | tailwindcss: {},
6 | autoprefixer: {},
7 | },
8 | }
9 |
--------------------------------------------------------------------------------
/spec_local/service_mongodb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: mongodb
5 | namespace: slot
6 | spec:
7 | selector:
8 | app: mongodb
9 | ports:
10 | - protocol: "TCP"
11 | port: 80
12 | targetPort: 27017
13 |
--------------------------------------------------------------------------------
/spec_local/service_envoy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: envoy
5 | namespace: slot
6 | spec:
7 | type: NodePort
8 | selector:
9 | app: envoy
10 | ports:
11 | - nodePort: 30002
12 | port: 8080
13 | targetPort: 80
14 |
--------------------------------------------------------------------------------
/spec_local/service_frapp.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: frapp
5 | namespace: slot
6 | spec:
7 | type: NodePort
8 | selector:
9 | app: frapp
10 | ports:
11 | - nodePort: 30001
12 | port: 8080
13 | targetPort: 80
14 |
--------------------------------------------------------------------------------
/spec_local/service_user_management.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: user-management
5 | namespace: slot
6 | spec:
7 | selector:
8 | app: user-management
9 | ports:
10 | - protocol: "TCP"
11 | port: 80
12 | targetPort: 80
--------------------------------------------------------------------------------
/spec_local/service_gateway.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: gateway
5 | namespace: slot
6 | spec:
7 | type: NodePort
8 | selector:
9 | app: gateway
10 | ports:
11 | - nodePort: 30000
12 | port: 8080
13 | targetPort: 3000
14 |
--------------------------------------------------------------------------------
/spec_local/service_next_frapp.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: v1
2 | kind: Service
3 | metadata:
4 | name: next-frapp
5 | namespace: slot
6 | spec:
7 | type: NodePort
8 | selector:
9 | app: next-frapp
10 | ports:
11 | - nodePort: 30003
12 | port: 8080
13 | targetPort: 3000
14 |
--------------------------------------------------------------------------------
/frontend/app/entry.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Check that we have REACT_APP_BASE_URL vars"
4 | test -n "$REACT_APP_BASE_URL"
5 |
6 | find /dist \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_REACT_APP_BASE_URL#$REACT_APP_BASE_URL#g"
7 |
8 | echo "Starting React"
9 | exec "$@"
--------------------------------------------------------------------------------
/frontend/next_app/next.config.js:
--------------------------------------------------------------------------------
1 |
2 | module.exports = {
3 | distDir: 'nextBuild',
4 | publicRuntimeConfig : {
5 | LOCAL_URL : process.env.LOCAL_URL,
6 | NEXTAUTH_URL: process.env.NEXTAUTH_URL,
7 | NEXT_PUBLIC_REACT_APP_BASE_URL: process.env.NEXT_PUBLIC_REACT_APP_BASE_URL,
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/next_app/pages/_app.js:
--------------------------------------------------------------------------------
1 | import 'tailwindcss/tailwind.css'
2 | import { SessionProvider } from "next-auth/react"
3 |
4 | export default function MyApp({ Component, pageProps: { session, ...pageProps } }) {
5 | return (
6 |
7 |
8 | )
9 | }
10 |
--------------------------------------------------------------------------------
/frontend/next_app/util/protoHelper.js:
--------------------------------------------------------------------------------
1 |
2 | var isLocal = process.env.NODE_ENV == "development"
3 |
4 | module.exports = {
5 | omitAuthCalls: () => process.env.OMIT_GATEWAY_AUTH_CALL == "true",
6 | getUrl: () => (process.env.NEXT_PUBLIC_REACT_APP_BASE_URL != undefined ? process.env.NEXT_PUBLIC_REACT_APP_BASE_URL : "PH_NEXT_PUBLIC_REACT_APP_BASE_URL")
7 |
8 | }
--------------------------------------------------------------------------------
/java/com/task/README.md:
--------------------------------------------------------------------------------
1 | # template Service
2 |
3 | All services regarding generation of schedules and evaludations of tasks.
4 |
5 | ## API
6 |
7 | - GenerateScheduleEntries:
8 | * For a given time slot generates task entries based on task templates
9 | * INPUT : (int) slot unix start time , (int) slot unix end time
10 | * OUTPUT : List
--------------------------------------------------------------------------------
/frontend/next_app/util/ReadMe.md:
--------------------------------------------------------------------------------
1 | This dir stores all js_out generated files to be used in frontend js applications. NEVER PUSH generated files to repo
2 |
3 | To generate all task service proto files for example , run :
4 | - protoc proto/task/*.proto --js_out=import_style=commonjs,binary:frontend/genProto --grpc-web_out=import_style=commonjs,mode=grpcwebtext:frontend/genProto/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Mono Repo Template
2 |
3 | - Bazel
4 | - gRPC + protobuf
5 | - java microservices
6 | - NextJS + React frontend apps
7 | - monogodb (container) for local testing
8 |
9 | 
10 |
11 |
12 | Start @ :
13 | - spec_local, should have container image target references
14 | - proto, should define microservices
15 |
16 |
--------------------------------------------------------------------------------
/frontend/envoy/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@io_bazel_rules_docker//container:container.bzl", "container_image")
4 |
5 | container_image(
6 | name = "envoy_image",
7 | base = "@envoy_base//image",
8 | files = [
9 | "envoy.yaml",
10 | ],
11 | cmd = " && ".join([
12 | "envoy -c ./envoy.yaml;"
13 | ]),
14 | )
--------------------------------------------------------------------------------
/frontend/app/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { render } from 'react-dom';
3 | import { BrowserRouter } from "react-router-dom";
4 |
5 | import HelloWorld from './HelloWorld';
6 | import Protected from './components/Protected';
7 |
8 | render(
9 |
10 |
11 |
12 |
13 | , document.getElementById('root'));
--------------------------------------------------------------------------------
/local/init.js:
--------------------------------------------------------------------------------
1 | var db = connect('127.0.0.1/localSlot');
2 |
3 | db.users.insert({
4 | 'user_id' : 11111,
5 | 'name': "Kunal Sample",
6 | 'email': "krp@sample.com" });
7 |
8 | db.refresh.insert({
9 | 'user_id' : 11111,
10 | 'token': "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMTExMSIsImlhdCI6MTY0MDMzNzcxNn0.DnBBwTC2z1fv6VItgFnt7kS5_KEwt0LmPxaW0VtAq7I"});
11 |
12 | db.users.createIndex({user_id:1}, {unique: true});
13 |
--------------------------------------------------------------------------------
/spec_local/ReadMe.md:
--------------------------------------------------------------------------------
1 | # Local Cluster of STack
2 |
3 |
4 | 1) start up minikube
5 | 2) ensure that cluser ip (`minikube ip`) points to `slotlocal.com`
6 | - add registry in /etc/hosts
7 | - this is the domain that GOOGLE SSO will redirect to
8 | 3) enable ingress : `minikube addons enable ingress`
9 | 4) run startup.sh
10 |
11 |
12 | As a placeholder, 'slot' was used as the namespace/image name (for registries)
13 |
14 |
--------------------------------------------------------------------------------
/proto/task/task_service.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package task;
4 |
5 | option java_package = "com.task";
6 | option java_outer_classname = "TaskServiceProto";
7 |
8 |
9 | service TaskService {
10 |
11 | rpc SomeAction(ActionRequest) returns (ActionResponse);
12 |
13 | }
14 |
15 | message ActionRequest {
16 | string some_data = 1;
17 | }
18 |
19 | message ActionResponse{
20 | repeated string result_data = 2;
21 | }
--------------------------------------------------------------------------------
/npm/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "npm",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@grpc/grpc-js": "^1.3.4",
13 | "google-protobuf": "^3.17.3",
14 | "grpc-tools": "^1.11.2",
15 | "ts-protoc-gen": "^0.15.0"
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/proto/task/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@rules_proto//proto:defs.bzl", "proto_library")
4 | load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library")
5 |
6 |
7 | proto_library(
8 | name = "task_service_proto",
9 | srcs = ["task_service.proto"],
10 | )
11 |
12 | java_grpc_library(
13 | name = "task_service_java_proto",
14 | protos = [
15 | ":task_service_proto",
16 | ],
17 | )
18 |
--------------------------------------------------------------------------------
/frontend/next_app/tailwind.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'jit',
3 | purge: ['./pages/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
4 | darkMode: false, // or 'media' or 'class'
5 | theme: {
6 | minWidth: {
7 | '0': '0',
8 | '1/12': '8%',
9 | '1/6': '17%',
10 | '1/4': '25%',
11 | 'full': '100%',
12 | }
13 | },
14 | variants: {
15 | extend: {},
16 | },
17 | plugins: [],
18 | }
19 |
--------------------------------------------------------------------------------
/.bazelrc:
--------------------------------------------------------------------------------
1 | build --incompatible_restrict_string_escapes=false --define=CLUSTER_CR=def_cluster --define=CLUSTER_NAME=def_cluster --define=CLUSTER_CONTEXT=def_cluster --define=CLUSTER_USER=def_cluster --define=CLUSTER_INGRESS_IP=def_cluster --define=GOOGLE_CLIENT_ID=def_google_client_id --define=GOOGLE_CLIENT_SECRET=def_google_client_secret --define=MONGODB_URI=def_mongodb_uri --define=MONGODB_DB=def_mongodb_db --define=DOMAIN=def_domain
2 | query --incompatible_restrict_string_escapes=false
--------------------------------------------------------------------------------
/spec_local/deployment_mongodb.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: mongodb
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: mongodb
11 | template:
12 | metadata:
13 | labels:
14 | app: mongodb
15 | spec:
16 | containers:
17 | - name: mongodb
18 | image: YOUR_REGISTRY_NAME/slot_mongodb
19 | ports:
20 | - containerPort: 80
21 |
--------------------------------------------------------------------------------
/spec_local/deployment_task.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: task
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: task
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: task
17 | spec:
18 | containers:
19 | - name: task
20 | image: YOUR_REGISTRY_NAME/slot_task
21 | ports:
22 | - containerPort: 80
23 |
--------------------------------------------------------------------------------
/java/com/util/ServiceModule.java:
--------------------------------------------------------------------------------
1 | package com.util;
2 |
3 | import com.google.inject.AbstractModule;
4 | import com.user.db.UserDBHandler;
5 | import com.user.db.MainUserDBHandler;
6 | import com.user.util.SSOValidator;
7 | import com.user.util.MainSSOValidator;
8 |
9 | public class ServiceModule extends AbstractModule{
10 |
11 | @Override
12 | protected void configure() {
13 | bind(UserDBHandler.class).to(MainUserDBHandler.class);
14 | bind(SSOValidator.class).to(MainSSOValidator.class);
15 | }
16 | }
--------------------------------------------------------------------------------
/spec_local/deployment_envoy.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: envoy
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: envoy
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: envoy
17 | spec:
18 | containers:
19 | - name: envoy
20 | image: YOUR_REGISTRY_NAME/slot_envoy
21 | ports:
22 | - containerPort: 80
23 |
--------------------------------------------------------------------------------
/java/com/util/FakeServiceModule.java:
--------------------------------------------------------------------------------
1 | package com.util;
2 |
3 | import com.google.inject.AbstractModule;
4 | import com.user.db.UserDBHandler;
5 | import com.user.db.FakeUserDBHandler;
6 | import com.user.util.SSOValidator;
7 | import com.user.util.FakeSSOValidator;
8 |
9 | public class FakeServiceModule extends AbstractModule{
10 |
11 | @Override
12 | protected void configure() {
13 | bind(UserDBHandler.class).to(FakeUserDBHandler.class);
14 | bind(SSOValidator.class).to(FakeSSOValidator.class);
15 | }
16 | }
--------------------------------------------------------------------------------
/frontend/next_app/pages/index.js:
--------------------------------------------------------------------------------
1 | import Head from 'next/head'
2 | import Login from '../components/Login'
3 |
4 | import { useState,useEffect} from 'react';
5 |
6 |
7 | export default function Home(props) {
8 |
9 |
10 | return (
11 |
12 |
13 |
Next Frapp
14 |
15 |
16 |
17 |
18 | Index
19 |
20 |
21 |
22 | )
23 | }
24 |
25 |
--------------------------------------------------------------------------------
/proto/user/user.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package user;
4 |
5 | option java_package = "com.user";
6 | option java_outer_classname = "UserProto";
7 |
8 | message UserId {
9 | int32 id = 1;
10 | }
11 |
12 | message UserRefreshToken {
13 | string data = 1;
14 | }
15 |
16 | message UserAccessToken {
17 | string data = 1;
18 | }
19 |
20 | message User{
21 | UserId user_id = 1;
22 | string name = 2;
23 | string email = 3;
24 | string profile_url = 4;
25 | string locale = 5;
26 | oneof sso_id {
27 | string google_user_id = 6;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Ignore backup files.
2 | *~
3 | # Ignore Vim swap files.
4 | .*.swp
5 | # Ignore files generated by IDEs.
6 | /.classpath
7 | /.factorypath
8 | /.idea/
9 | /.ijwb/
10 | /.project
11 | /.settings
12 | /.vscode/
13 | /bazel.iml
14 | # Ignore all bazel-* symlinks. There is no full list since this can change
15 | # based on the name of the directory bazel is cloned into.
16 | /bazel-*
17 |
18 | .vagrant/
19 | Vagrantfile
20 | .idea/**
21 | *.iml
22 | .DS_Store
23 | **/.DS_Store
24 | node_modules/
25 |
26 | spec_*/config
27 |
28 | **/genProto
29 |
30 |
31 |
--------------------------------------------------------------------------------
/frontend/next_app/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # next.js
12 | /.next/
13 | /out/
14 |
15 | # production
16 |
17 | # misc
18 | .DS_Store
19 | *.pem
20 |
21 | # debug
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
26 | # local env files
27 | .env.local
28 | .env.development.local
29 | .env.test.local
30 | .env.production.local
31 |
32 | # vercel
33 | .vercel
34 |
35 | nextBuild
36 |
37 | .env
--------------------------------------------------------------------------------
/frontend/app/src/protoHelper.js:
--------------------------------------------------------------------------------
1 | const LOCAL_PATH = './genProto/proto/task/'
2 | const BAZEL_PATH = './proto/task/'
3 |
4 | const PATH = process.env.LOCAL_URL ? LOCAL_PATH : BAZEL_PATH
5 |
6 | const taskServiceProto = require(PATH+'task_service_grpc_web_pb.js')
7 |
8 | var getUrl = (process.env.LOCAL_URL ? process.env.LOCAL_URL : "PH_REACT_APP_BASE_URL");
9 |
10 | module.exports = {
11 | getUrl: () => getUrl,
12 | getTaskServiceProto: () => taskServiceProto,
13 | getTaskServiceClient: () => new taskServiceProto.TaskServiceClient(
14 | getUrl + "/gapi", null, {'withCredentials': true})
15 | }
--------------------------------------------------------------------------------
/spec_local/deployment_frapp.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: frapp
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: frapp
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: frapp
17 | spec:
18 | containers:
19 | - name: frapp
20 | image: YOUR_REGISTRY_NAME/slot_frapp
21 | ports:
22 | - containerPort: 80
23 | env:
24 | - name: "REACT_APP_BASE_URL"
25 | value: "http://{SUB_BASE_URL}"
26 |
--------------------------------------------------------------------------------
/local/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@io_bazel_rules_docker//container:container.bzl", "container_image")
4 |
5 |
6 | genrule(
7 | name = "init_files",
8 | srcs = [
9 | ":init.js",
10 | ],
11 | cmd = " && ".join([
12 | "mkdir docker-entrypoint-initdb.d",
13 | "mv local/init.js docker-entrypoint-initdb.d",
14 | "mv ./docker-entrypoint-initdb.d $@",
15 | ]),
16 | outs = [
17 | "docker-entrypoint-initdb.d",
18 | ],
19 | )
20 |
21 | container_image(
22 | name = "db_local_image",
23 | base = "@mongo_base//image",
24 | files = [
25 | ":init_files"
26 | ],
27 | )
--------------------------------------------------------------------------------
/spec_local/deployment_gateway.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: gateway
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: gateway
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: gateway
17 | spec:
18 | containers:
19 | - name: gateway
20 | image: YOUR_REGISTRY_NAME/slot_gateway
21 | ports:
22 | - containerPort: 3000
23 | env:
24 | - name: "REACT_APP_BASE_URL"
25 | value: "http://{SUB_BASE_URL}"
26 |
--------------------------------------------------------------------------------
/frontend/app/src/components/Protected.js:
--------------------------------------------------------------------------------
1 | import useAuthData from '../hooks/useAuthData'
2 | import React, { useState, useEffect } from 'react';
3 | import {getUrl} from '../protoHelper';
4 |
5 | export default function Protected(props){
6 |
7 | const [loadingState, accessToken] = useAuthData();
8 |
9 | useEffect(() => {
10 | console.log("protected ")
11 | console.log(loadingState)
12 | },[loadingState])
13 |
14 | console.log(loadingState)
15 | if(loadingState == "SUCCESS" || (loadingState == "IN_PROGRESS" && accessToken != null) ){
16 | return (props.children)
17 | }else if(loadingState == "FAILED"){
18 | window.location.assign(getUrl());
19 | return "FAILED"
20 | }
21 | else{
22 | return "LOADING..."
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/frontend/next_app/entry.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | echo "Check that we have REACT_APP_BASE_URL,PH_GOOGLE_CLIENT_ID,PH_GOOGLE_CLIENT_SECRET vars"
4 | test -n "$REACT_APP_BASE_URL"
5 | test -n "$PH_GOOGLE_CLIENT_ID"
6 | test -n "$PH_GOOGLE_CLIENT_SECRET"
7 |
8 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_NEXT_PUBLIC_REACT_APP_BASE_URL#$NEXT_PUBLIC_REACT_APP_BASE_URL#g"
9 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_GOOGLE_CLIENT_ID#$GOOGLE_CLIENT_ID#g"
10 | find prodBuild/nextBuild \( -type d -name .git -prune \) -o -type f -print0 | xargs -0 sed -i "s#PH_GOOGLE_CLIENT_SECRET#$GOOGLE_CLIENT_SECRET#g"
11 |
12 | echo "Starting React"
13 | exec "$@"
--------------------------------------------------------------------------------
/frontend/next_app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "next_app",
3 | "version": "1.0.0",
4 | "description": "NextJs frontend app ",
5 | "private": true,
6 | "scripts": {
7 | "dev": "next dev",
8 | "build": "next build",
9 | "start": "next start"
10 | },
11 | "dependencies": {
12 | "@grpc/grpc-js": "^1.4.5",
13 | "axios": "^0.24.0",
14 | "cookies": "^0.8.0",
15 | "google-protobuf": "3.19.0",
16 | "grpc-tools": "^1.11.2",
17 | "grpc-web": "1.2.1",
18 | "next": "11.1.2",
19 | "next-auth": "^4.0.6",
20 | "react": "^17.0.2",
21 | "react-dom": "^17.0.2",
22 | "tailwindcss": "^2.2.17"
23 | },
24 | "devDependencies": {
25 | "autoprefixer": "^10.2.6",
26 | "postcss": "^8.3.5"
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/frontend/next_app/components/Login.js:
--------------------------------------------------------------------------------
1 | import {useEffect} from 'react'
2 | import { useSession, signIn } from "next-auth/react"
3 | import { useRouter } from 'next/router'
4 | import {getUrl} from '../util/protoHelper'
5 |
6 | export default function Login(){
7 |
8 | const router = useRouter()
9 | const {data: session, status } = useSession()
10 |
11 | if (status === "authenticated"){
12 | return (
13 | <>
14 |
15 | Signed in as {session.user.email}
16 |
17 | >
18 | )
19 | }
20 | return (
21 | <>
22 | Not signed in
23 |
24 | >
25 | )
26 |
27 | }
--------------------------------------------------------------------------------
/java/com/user/util/FakeSSOValidator.java:
--------------------------------------------------------------------------------
1 | package com.user.util;
2 |
3 | import java.util.Map;
4 | import java.util.HashMap;
5 | import java.util.Optional;
6 | import com.user.UserProto.User;
7 | import com.google.inject.Singleton;
8 |
9 | @Singleton
10 | public class FakeSSOValidator implements SSOValidator {
11 |
12 | private Map tokenIdMap = new HashMap();
13 |
14 | @Override
15 | public Optional validateGoogleIdToken(String idToken){
16 | if(tokenIdMap.containsKey(idToken)){
17 | return Optional.of(tokenIdMap.get(idToken));
18 | }
19 | return Optional.empty();
20 | }
21 |
22 | public void clear(){
23 | tokenIdMap = new HashMap();
24 | }
25 |
26 | public void setUserToIdToken(String idToken, User user){
27 | tokenIdMap.put(idToken, user);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/java/com/task/TaskService.java:
--------------------------------------------------------------------------------
1 | package com.task;
2 |
3 | import com.util.ServiceModule;
4 | import com.google.inject.Injector;
5 | import com.google.inject.Guice;
6 | import io.grpc.Server;
7 | import io.grpc.ServerBuilder;
8 | import com.util.SetupUtil;
9 |
10 | public class TaskService {
11 |
12 | public static void main(String[] args) throws Exception {
13 |
14 | Injector injector = Guice.createInjector(new ServiceModule());
15 |
16 | Server pollServer =
17 | ServerBuilder
18 | .forPort(SetupUtil.DEFAULT_SERVICE_PORT)
19 | .intercept(new AuthenticatedInterceptor())
20 | .addService(injector.getInstance(TaskServiceImpl.class))
21 | .build();
22 | pollServer.start();
23 | pollServer.awaitTermination();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/java/com/task/Constants.java:
--------------------------------------------------------------------------------
1 | package com.task;
2 |
3 | import io.grpc.Metadata;
4 | import io.grpc.Context;
5 | import com.user.UserProto.UserId;
6 |
7 | public class Constants {
8 |
9 | // Metadata
10 | public static final Metadata.Key COOKIE_SLOT_METADATA_KEY =
11 | Metadata.Key.of("cookie", Metadata.ASCII_STRING_MARSHALLER);
12 |
13 | public static final Metadata.Key ACCESS_SLOT_METADATA_KEY =
14 | Metadata.Key.of("slot-a-tkn", Metadata.ASCII_STRING_MARSHALLER);
15 |
16 | public static final String CUSTOM_SLOT_HEADER_COOKIE_KEY = "custom_slot_header";
17 |
18 | // Context
19 | public static final Context.Key CUSTOM_HEADER_CTX_KEY = Context.key("custom_slot");
20 | public static final Context.Key USER_ID_CTX_KEY = Context.key("slot_user_id");
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/spec_local/deployment_user_management.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: user-management
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: user-management
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: user-management
17 | spec:
18 | containers:
19 | - name: user-management
20 | image: YOUR_REGISTRY_NAME/slot_user_management
21 | ports:
22 | - containerPort: 80
23 | env:
24 | - name: "GOOGLE_CLIENT_ID"
25 | value: "{GOOGLE_CLIENT_ID}"
26 | - name: "MONGODB_URI"
27 | value: "{MONGODB_URI}"
28 | - name: "MONGODB_DB"
29 | value: "{MONGODB_DB}"
30 |
--------------------------------------------------------------------------------
/java/com/util/SetupUtil.java:
--------------------------------------------------------------------------------
1 | package com.util;
2 |
3 | import java.security.Provider.Service;
4 | import java.util.Optional;
5 | import com.google.common.collect.ImmutableMap;
6 |
7 | /**
8 | *
9 | * Util to handle configuration and fetching of setup information for microservices. Servers: Call
10 | * to get port number for server instantiation Client: Call to get target address for said server
11 | */
12 |
13 | public class SetupUtil {
14 |
15 | public static int DEFAULT_SERVICE_PORT = 80;
16 |
17 | // All availabe services
18 | public enum AvailableServices {
19 | TASK,
20 | USER
21 | }
22 |
23 | public static String getTarget(AvailableServices service) {
24 | switch (service) {
25 | case TASK:
26 | return "task";
27 | case USER:
28 | return "user";
29 | }
30 | return "someurl";
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/java/test/com/task/BUILD:
--------------------------------------------------------------------------------
1 | load("//:java/test/java_testing_generator.bzl", "java_unit_test")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | java_unit_test(
6 | name = "task_tests",
7 | pkg="com.task",
8 | srcs = glob(["*.java"]),
9 | deps = [
10 | "@io_grpc_grpc_java//api",
11 | "@io_grpc_grpc_java//core:inprocess",
12 | "@io_grpc_grpc_java//stub",
13 | "@maven2//:io_grpc_grpc_testing",
14 | "@maven2//:com_google_inject_guice",
15 | "@maven2//:org_junit_jupiter_junit_jupiter_api",
16 | "//proto/task:task_service_java_proto",
17 | "//proto/user:user_java_proto",
18 | "//java/com/task:task_service_impl",
19 | "//java/com/task:authenticated_interceptor",
20 | "//java/com/task:constants",
21 | "//java/com/util:fake_service_module",
22 | "//java/com/user/util:jwt_util",
23 | ],
24 |
25 | )
26 |
--------------------------------------------------------------------------------
/nodejs/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nodejs",
3 | "version": "0.1.0",
4 | "private": true,
5 | "devDependencies": {
6 | "@bazel/bazelisk": "latest",
7 | "@bazel/buildifier": "latest",
8 | "@bazel/ibazel": "latest"
9 | },
10 | "scripts": {
11 | "build": "bazel build --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 //...",
12 | "run": "bazel run --platforms @build_bazel_rules_nodejs//toolchains/node:linux_amd64 //...",
13 | "test": "bazel test //..."
14 | },
15 | "dependencies": {
16 | "@grpc/grpc-js": "1.1.7",
17 | "body-parser": "^1.19.1",
18 | "cookie-parser": "^1.4.6",
19 | "express": "^4.17.1",
20 | "fs": "^0.0.1-security",
21 | "google-protobuf": "^3.17.3",
22 | "grpc-tools": "1.9.1",
23 | "grpc-web": "1.2.1",
24 | "ts-protoc-gen": "^0.15.0"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/spec_local/deployment_next_frapp.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: apps/v1
2 | kind: Deployment
3 | metadata:
4 | name: next-frapp
5 | namespace: slot
6 | spec:
7 | replicas: 1
8 | selector:
9 | matchLabels:
10 | app: next-frapp
11 | template:
12 | metadata:
13 | annotations:
14 | linkerd.io/inject: enabled
15 | labels:
16 | app: next-frapp
17 | spec:
18 | containers:
19 | - name: next-frapp
20 | image: YOUR_REGISTRY_NAME/slot_next_frapp
21 | ports:
22 | - containerPort: 80
23 | env:
24 | - name: "NEXT_PUBLIC_REACT_APP_BASE_URL"
25 | value: "http://{SUB_BASE_URL}"
26 | - name: "NEXTAUTH_URL"
27 | value: "http://{NEXTAUTH_URL}"
28 | - name: "GOOGLE_CLIENT_ID"
29 | value: "{GOOGLE_CLIENT_ID}"
30 | - name: "GOOGLE_CLIENT_SECRET"
31 | value: "{GOOGLE_CLIENT_SECRET}"
32 |
--------------------------------------------------------------------------------
/java/com/user/db/UserDBHandler.java:
--------------------------------------------------------------------------------
1 | package com.user.db;
2 |
3 | import java.util.Optional;
4 | import java.util.List;
5 | import com.google.common.util.concurrent.ListenableFuture;
6 | import com.user.UserDBProto.CreateUserRequest;
7 | import com.user.UserDBProto.CreateUserResponse;
8 | import com.user.UserDBProto.DBFetchUserRequest;
9 | import com.user.UserDBProto.DBFetchUserResponse;
10 | import com.user.UserDBProto.UpdateRefreshTokenRequest;
11 | import com.user.UserDBProto.UpdateRefreshTokenResponse;
12 |
13 | /*
14 | DB Util for all user related operations
15 | */
16 | public interface UserDBHandler {
17 |
18 | //fetch
19 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest);
20 |
21 | //update
22 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest);
23 | public ListenableFuture createUser(CreateUserRequest createUserRequest);
24 | }
25 |
--------------------------------------------------------------------------------
/java/test/com/user/management/BUILD:
--------------------------------------------------------------------------------
1 | load("//:java/test/java_testing_generator.bzl", "java_unit_test")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | java_unit_test(
6 | name = "user_tests",
7 | pkg="com.user.management",
8 | srcs = glob(["*.java"]),
9 | deps = [
10 | "@io_grpc_grpc_java//api",
11 | "@io_grpc_grpc_java//core:inprocess",
12 | "@io_grpc_grpc_java//stub",
13 | "@maven2//:io_grpc_grpc_testing",
14 | "@maven2//:com_google_inject_guice",
15 | "@maven2//:org_junit_jupiter_junit_jupiter_api",
16 | "//proto/user:user_management_service_java_proto",
17 | "//proto/user:user_java_proto",
18 | "//java/com/user/management:user_management_service_impl",
19 | "//java/com/util:fake_service_module",
20 | "//java/com/user/db:fake_user_db_handler",
21 | "//java/com/user/util:jwt_util",
22 | "//java/com/user/util:fake_sso_validator"
23 | ],
24 |
25 | )
26 |
--------------------------------------------------------------------------------
/proto/user/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@rules_proto//proto:defs.bzl", "proto_library")
4 | load("@rules_java//java:defs.bzl", "java_proto_library")
5 | load("@rules_proto_grpc//java:defs.bzl", "java_grpc_library")
6 |
7 |
8 | proto_library(
9 | name = "user_proto",
10 | srcs = ["user.proto"],
11 | )
12 |
13 | java_proto_library(
14 | name = "user_java_proto",
15 | deps = [":user_proto"],
16 | )
17 |
18 | proto_library(
19 | name = "user_db_proto",
20 | srcs = ["db.proto"],
21 | deps = [":user_proto"]
22 | )
23 |
24 | java_proto_library(
25 | name = "user_db_java_proto",
26 | deps = [":user_db_proto"],
27 | )
28 |
29 |
30 | proto_library(
31 | name = "user_management_service_proto",
32 | srcs = ["user_management_service.proto"],
33 | deps = [":user_proto"]
34 | )
35 |
36 | java_grpc_library(
37 | name = "user_management_service_java_proto",
38 | protos = [
39 | ":user_management_service_proto",
40 | ":user_proto"
41 | ],
42 | )
--------------------------------------------------------------------------------
/java/com/user/management/UserManagementService.java:
--------------------------------------------------------------------------------
1 | package com.user.management;
2 |
3 | import com.util.ServiceModule;
4 | import com.google.inject.Injector;
5 | import com.google.inject.Guice;
6 | import io.grpc.Server;
7 | import io.grpc.ServerBuilder;
8 | import com.util.SetupUtil;
9 |
10 | public class UserManagementService {
11 |
12 | public static void main(String[] args) throws Exception {
13 |
14 | Injector injector = Guice.createInjector(new ServiceModule());
15 |
16 | System.out.println("user management service");
17 | System.out.println(System.getenv("GOOGLE_CLIENT_ID"));
18 | System.out.println(System.getenv("MONGODB_URI"));
19 | System.out.println(System.getenv("MONGODB_DB"));
20 |
21 | Server pollServer =
22 | ServerBuilder
23 | .forPort(SetupUtil.DEFAULT_SERVICE_PORT)
24 | .addService(injector.getInstance(UserManagementServiceImpl.class))
25 | .build();
26 | pollServer.start();
27 | pollServer.awaitTermination();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/frontend/next_app/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/spec_local/ingress.yaml:
--------------------------------------------------------------------------------
1 | apiVersion: networking.k8s.io/v1
2 | kind: Ingress
3 | metadata:
4 | name: ing
5 | namespace: slot
6 | annotations:
7 | nginx.ingress.kubernetes.io/rewrite-target: /$1
8 | spec:
9 | rules:
10 | - http:
11 | paths:
12 | - path: /frapp(?:/|$)?(.*)
13 | pathType: Prefix
14 | backend:
15 | service:
16 | name: frapp
17 | port:
18 | number: 8080
19 | - path: /gateway(?:/|$)?(.*)
20 | pathType: Prefix
21 | backend:
22 | service:
23 | name: gateway
24 | port:
25 | number: 8080
26 | - path: /gapi(?:/|$)?(.*)
27 | pathType: Prefix
28 | backend:
29 | service:
30 | name: envoy
31 | port:
32 | number: 8080
33 | - path: /?(.*)
34 | pathType: Prefix
35 | backend:
36 | service:
37 | name: next-frapp
38 | port:
39 | number: 8080
--------------------------------------------------------------------------------
/frontend/ReadMe.md:
--------------------------------------------------------------------------------
1 | ##Frontend Apps
2 |
3 | React app will be protectd by access token rotation
4 |
5 | For non k8s testing:
6 |
7 | **NOTE** : there may be complication's w/ user auth if running outside of k8s cluster, might need to add signal in backend to not validate
8 |
9 | 1) generate the proto js files, will need to install `protoc`
10 | - use `genProto` dir in react app `util` to store all the files
11 |
12 | Example proto generation:
13 | protoc proto/DIRECTORY_PATH*.proto --js_out=import_style=commonjs,binary:frontend/app/util/genProto/ --grpc-web_out=import_style=commonjs,mode=grpcwebtext:frontend/app/util/genProto/
14 |
15 |
16 | 2)run local cluster & expose envoy service : spec_local/startup.sh && minikube service envoy -n slot
17 |
18 | 3)run webpack dev locally w/ webpack.config.js : LOCAL_URL=ENVOY_SERVICE_URL npm run dev
19 |
20 | #Run Dev server
21 | - ibazel run //frontend/app:dev_server --REACT_APP_BASE_URL=
22 |
23 | #Run Prod Server
24 | - http local : ibazel run //frontend/app:dev_server --REACT_APP_BASE_URL=
25 | - docker image gen : bazel run //frontend/app:prod_image
26 |
27 |
28 |
--------------------------------------------------------------------------------
/proto/user/user_management_service.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package user_management;
4 |
5 | option java_package = "com.user.management";
6 | option java_outer_classname = "UserManagementServiceProto";
7 |
8 | import 'proto/user/user.proto';
9 |
10 | service UserManagementService {
11 |
12 | // validate existing refresh then create new refresh/access token
13 | rpc RegenerateRefreshToken (RegenerateRefreshTokenRequest) returns (RegenerateRefreshTokenResponse);
14 |
15 | rpc SignIn ( SignInRequest) returns (SignInResponse);
16 |
17 | }
18 |
19 | message RegenerateRefreshTokenRequest{
20 |
21 | oneof credential {
22 | user.UserRefreshToken existing_refresh_token = 1;
23 | }
24 | }
25 |
26 | message RegenerateRefreshTokenResponse{
27 | user.UserRefreshToken refresh_token = 1;
28 | user.UserAccessToken access_token = 2;
29 | }
30 |
31 | message SignInRequest {
32 | string id_token = 1;
33 |
34 | enum Provider {
35 | UNKNOWN = 0;
36 | GOOGLE = 1;
37 | }
38 |
39 | Provider provider = 2;
40 | }
41 |
42 | message SignInResponse{
43 | user.UserRefreshToken refresh_token = 1;
44 | user.UserAccessToken access_token = 2;
45 | }
--------------------------------------------------------------------------------
/proto/user/db.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package db;
4 |
5 | option java_package = "com.user";
6 | option java_outer_classname = "UserDBProto";
7 |
8 | import 'proto/user/user.proto';
9 |
10 | // fetches all user details by field
11 | message DBFetchUserRequest {
12 |
13 | oneof identifier {
14 | user.UserId user_id = 1;
15 | string email = 3;
16 | }
17 |
18 | enum FetchableFields{
19 | USER_FIELD_UNKNOWN = 0;
20 | USER_FIELD_USER = 2;
21 | USER_FIELD_REFRESH_TOKEN = 1;
22 |
23 | }
24 |
25 | repeated FetchableFields field = 2;
26 |
27 | }
28 |
29 | message DBFetchUserResponse {
30 |
31 | user.UserRefreshToken refresh_token = 1;
32 | user.User user = 2;
33 |
34 | }
35 |
36 | message UpdateRefreshTokenRequest{
37 | user.UserId user_id = 1;
38 | user.UserRefreshToken new_refresh_token = 2;
39 | }
40 |
41 | message UpdateRefreshTokenResponse{
42 | user.UserRefreshToken new_refresh_token = 1;
43 | }
44 |
45 | message CreateUserRequest{
46 | user.User user = 1;
47 | }
48 |
49 | message CreateUserResponse{
50 | enum Error{
51 | CREATION_ERROR_UNKOWN = 0;
52 | }
53 |
54 | Error error = 1;
55 |
56 | user.UserId created_user_id = 2;
57 | }
--------------------------------------------------------------------------------
/frontend/app/nginx.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 |
3 | worker_processes auto;
4 | error_log /var/log/nginx/error.log warn;
5 | pid /var/run/nginx.pid;
6 | # since docker is one process per container, no need to run in background
7 | daemon off;
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 | http {
13 | include /etc/nginx/mime.types;
14 | default_type application/octet-stream;
15 | log_format main ‘$remote_addr — $remote_user [$time_local] “$request” ‘
16 | ‘$status $body_bytes_sent “$http_referer” ‘
17 | ‘“$http_user_agent” “$http_x_forwarded_for”’;
18 | access_log /var/log/nginx/access.log main;
19 |
20 | server {
21 | listen 80;
22 |
23 | location = /status {
24 | access_log off;
25 | default_type text/plain;
26 | add_header Content-Type text/plain;
27 | return 200 "good";
28 | }
29 |
30 | location / {
31 | gzip off;
32 | root /dist/;
33 | index index.html;
34 | }
35 |
36 | location ~* \.(js|jpg|png|css)$ {
37 | root /dist/;
38 | }
39 | }
40 |
41 | sendfile on;
42 | keepalive_timeout 65;
43 | }
44 |
--------------------------------------------------------------------------------
/frontend/app/nginxDev.conf:
--------------------------------------------------------------------------------
1 | user nginx;
2 |
3 | worker_processes auto;
4 | error_log /var/log/nginx/error.log warn;
5 | pid /var/run/nginx.pid;
6 | # since docker is one process per container, no need to run in background
7 | daemon off;
8 |
9 | events {
10 | worker_connections 1024;
11 | }
12 | http {
13 | include /etc/nginx/mime.types;
14 | default_type application/octet-stream;
15 | log_format main ‘$remote_addr — $remote_user [$time_local] “$request” ‘
16 | ‘$status $body_bytes_sent “$http_referer” ‘
17 | ‘“$http_user_agent” “$http_x_forwarded_for”’;
18 | access_log /var/log/nginx/access.log main;
19 |
20 | server {
21 | listen 80;
22 |
23 | location = /status {
24 | access_log off;
25 | default_type text/plain;
26 | add_header Content-Type text/plain;
27 | return 200 "good";
28 | }
29 |
30 | location / {
31 | gzip off;
32 | root /devDist/;
33 | index index.html;
34 | }
35 |
36 | location ~* \.(js|jpg|png|css)$ {
37 | root /devDist/;
38 | }
39 | }
40 |
41 | sendfile on;
42 | keepalive_timeout 65;
43 | }
44 |
--------------------------------------------------------------------------------
/java/com/user/management/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_java//java:defs.bzl", "java_library")
2 | load("@io_bazel_rules_docker//java:image.bzl", "java_image")
3 |
4 | package(default_visibility = ["//visibility:public"])
5 |
6 |
7 | java_library(
8 | name = "user_management_service_impl",
9 | srcs = ["UserManagementServiceImpl.java"],
10 | deps = [
11 | "@maven2//:com_google_inject_guice",
12 | "@io_grpc_grpc_java//api",
13 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos",
14 | "//proto/user:user_management_service_java_proto",
15 | "//java/com/user/db:user_db_handler",
16 | "//proto/user:user_db_java_proto",
17 | "//java/com/user/util:jwt_util",
18 | "//java/com/user/util:sso_validator"
19 | ],
20 | )
21 |
22 |
23 | java_image(
24 | name = "user_management_java_image",
25 | srcs = ["UserManagementService.java"],
26 | deps = [
27 | "@maven2//:com_google_inject_guice",
28 | "@io_grpc_grpc_java//api",
29 | ":user_management_service_impl",
30 | "//java/com/util:service_module",
31 | "//java/com/util:setup-util",
32 | ],
33 | main_class = "com.user.management.UserManagementService",
34 | )
--------------------------------------------------------------------------------
/nodejs/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_node_library","js_proto_library")
4 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image")
5 |
6 | js_grpc_node_library(
7 | name = "task_service_proto",
8 | protos = [
9 | "//proto/task:task_service_proto",
10 | "//proto/user:user_proto",
11 | ],
12 | deps_repo = "@nodejs_modules"
13 | )
14 |
15 | js_grpc_node_library(
16 | name = "user_management_service_proto",
17 | protos = [
18 | "//proto/user:user_management_service_proto",
19 | "//proto/user:user_proto"
20 | ],
21 | deps_repo = "@nodejs_modules"
22 | )
23 |
24 | js_proto_library(
25 | name = "user_js_proto",
26 | protos = ["//proto/user:user_proto"],
27 | deps_repo = "@nodejs_modules"
28 | )
29 |
30 |
31 | nodejs_image(
32 | name = "nodejs_gateway_image",
33 | entry_point = "gateway.js",
34 | data = [
35 | "@nodejs_modules//express",
36 | "@nodejs_modules//cookie-parser",
37 | "@nodejs_modules//@grpc/grpc-js",
38 | "@nodejs_modules//body-parser",
39 | ":task_service_proto",
40 | ":user_management_service_proto",
41 | ":user_js_proto",
42 | ],
43 | )
44 |
--------------------------------------------------------------------------------
/java/com/util/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_java//java:defs.bzl", "java_library")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | # The java_binary rule, like many others, has multiple outputs. We'll
6 | # see that in action shortly.
7 |
8 | java_library(
9 | name = "setup-util",
10 | srcs = ["SetupUtil.java"],
11 | deps = [
12 | "@maven//:com_google_guava_guava",
13 | ],
14 | )
15 |
16 | java_library(
17 | name = "service_module",
18 | srcs = ["ServiceModule.java"],
19 | deps = [
20 | "@maven2//:com_google_inject_guice",
21 | "@maven2//:com_google_guava_guava",
22 | "//java/com/user/db:user_db_handler",
23 | "//java/com/user/db:main_user_db_handler",
24 | "//java/com/user/util:sso_validator",
25 | "//java/com/user/util:main_sso_validator",
26 | ],
27 | )
28 |
29 | java_library(
30 | name = "fake_service_module",
31 | srcs = ["FakeServiceModule.java"],
32 | deps = [
33 | "@maven2//:com_google_inject_guice",
34 | "@maven2//:com_google_guava_guava",
35 | "//java/com/user/db:user_db_handler",
36 | "//java/com/user/db:fake_user_db_handler",
37 | "//java/com/user/util:sso_validator",
38 | "//java/com/user/util:fake_sso_validator",
39 | ],
40 | )
41 |
--------------------------------------------------------------------------------
/spec_local/startup.sh:
--------------------------------------------------------------------------------
1 | kubectl config view >> spec_local/config;
2 | export CLUSTER_INGRESS_IP="";
3 | export DOMAIN="slotlocal.com";
4 | export MONGODB_URI="mongodb";
5 | export MONGODB_DB="localSlot";
6 | export GOOGLE_CLIENT_ID="ENTER_CLIENT_ID";
7 | export GOOGLE_CLIENT_SECRET="ENTER_CLIENT_SECRET";
8 | while [ -z $CLUSTER_INGRESS_IP ]; do echo "Waiting for minikube end point..."; CLUSTER_INGRESS_IP=$(minikube ip ); [ -z "$CLUSTER_INGRESS_IP" ] && sleep 10; done; echo "End point ready-" && echo $CLUSTER_INGRESS_IP;
9 | # bazel run //spec_local:controllers.apply;
10 | #linkerd check;
11 | bazel run //spec_local:ns_slot.apply;
12 | bazel run //spec_local:k8s_1.apply --define=CLUSTER_INGRESS_IP=$CLUSTER_INGRESS_IP --define=MONGODB_URI=$MONGODB_URI --define=MONGODB_DB=$MONGODB_DB --define=GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID --define=GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET --define=DOMAIN=$DOMAIN;
13 | bazel run //spec_local:k8s_2.apply --define=CLUSTER_INGRESS_IP=$CLUSTER_INGRESS_IP --define=MONGODB_URI=$MONGODB_URI --define=MONGODB_DB=$MONGODB_DB --define=GOOGLE_CLIENT_ID=$GOOGLE_CLIENT_ID --define=GOOGLE_CLIENT_SECRET=$GOOGLE_CLIENT_SECRET --define=DOMAIN=$DOMAIN;
14 | bazel run //spec_local:nodejs_deployment.apply --platforms=@build_bazel_rules_nodejs//toolchains/node:linux_amd64 --define=DOMAIN=$DOMAIN;
--------------------------------------------------------------------------------
/java/com/user/util/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 |
4 | java_library(
5 | name = "jwt_util",
6 | srcs = ["JWTUtil.java"],
7 | deps = [
8 | "@jjwt//:io_jsonwebtoken_jjwt",
9 | "@jjwt//:javax_xml_bind_jaxb_api",
10 | "//proto/user:user_java_proto",
11 | ]
12 | )
13 |
14 | java_library(
15 | name = "sso_validator",
16 | srcs = ["SSOValidator.java"],
17 | deps = [
18 | "@maven2//:com_google_api_client_google_api_client",
19 | "//proto/user:user_java_proto",
20 | ]
21 | )
22 |
23 | java_library(
24 | name = "main_sso_validator",
25 | srcs = ["MainSSOValidator.java"],
26 | deps = [
27 | ":sso_validator",
28 | "@maven2//:com_google_api_client_google_api_client",
29 | "@maven2//:com_google_http_client_google_http_client",
30 | "@maven2//:com_google_http_client_google_http_client_gson",
31 | "//proto/user:user_java_proto",
32 | ]
33 | )
34 |
35 | java_library(
36 | name = "fake_sso_validator",
37 | srcs = ["FakeSSOValidator.java"],
38 | deps = [
39 | ":sso_validator",
40 | "@maven2//:com_google_api_client_google_api_client",
41 | "@maven2//:com_google_inject_guice",
42 | "//proto/user:user_java_proto",
43 | ]
44 | )
--------------------------------------------------------------------------------
/java/com/user/db/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_java//java:defs.bzl","java_library")
2 |
3 | package(default_visibility = ["//visibility:public"])
4 |
5 | java_library(
6 | name = "user_db_handler",
7 | srcs = ["UserDBHandler.java"],
8 | deps = [
9 | "@maven2//:com_google_guava_guava",
10 | "//proto/user:user_java_proto",
11 | "//proto/user:user_db_java_proto",
12 | ],
13 | )
14 |
15 | java_library(
16 | name = "fake_user_db_handler",
17 | srcs = ["FakeUserDBHandler.java"],
18 | deps = [
19 | "@maven2//:com_google_guava_guava",
20 | "@maven2//:com_google_inject_guice",
21 | ":user_db_handler",
22 | "//proto/user:user_java_proto",
23 | "//proto/user:user_db_java_proto",
24 | ],
25 | )
26 |
27 | java_library(
28 | name = "main_user_db_handler",
29 | srcs = ["MainUserDBHandler.java"],
30 | deps = [
31 | "@maven2//:com_google_guava_guava",
32 | "@maven2//:com_google_inject_guice",
33 | "@maven2//:org_mongodb_mongodb_driver_sync",
34 | "@maven2//:org_mongodb_bson",
35 | "@maven2//:org_mongodb_mongo_java_driver",
36 | "@maven2//:org_slf4j_slf4j_simple",
37 | ":user_db_handler",
38 | "//proto/user:user_java_proto",
39 | "//proto/user:user_db_java_proto",
40 | "//java/com/user/util:jwt_util"
41 | ],
42 | )
--------------------------------------------------------------------------------
/frontend/app/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "app",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "src/index.js",
6 | "scripts": {
7 | "dev": "webpack serve --mode=development --open --hot"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "dependencies": {
12 | "@grpc/grpc-js": "^1.4.5",
13 | "@material-ui/core": "^4.11.4",
14 | "axios": "^0.24.0",
15 | "buffer": "^6.0.3",
16 | "copy-webpack-plugin": "^9.0.0",
17 | "crypto-browserify": "^3.12.0",
18 | "fs": "^0.0.1-security",
19 | "google-protobuf": "^3.17.3",
20 | "grpc-tools": "^1.11.2",
21 | "grpc-web": "^1.2.1",
22 | "http_server": "^1.0.12",
23 | "jsonwebtoken": "^8.5.1",
24 | "process": "^0.11.10",
25 | "react": "^17.0.2",
26 | "react-dom": "^17.0.2",
27 | "react-router-dom": "^6.2.1",
28 | "stream-browserify": "^3.0.0",
29 | "ts-protoc-gen": "^0.15.0",
30 | "util": "^0.12.4",
31 | "webpack-node-externals": "^3.0.0"
32 | },
33 | "devDependencies": {
34 | "@babel/core": "^7.14.2",
35 | "@babel/preset-env": "^7.14.2",
36 | "@babel/preset-react": "^7.13.13",
37 | "babel-loader": "^8.2.2",
38 | "copy-webpack-plugin": "^9.0.0",
39 | "css-loader": "^5.2.4",
40 | "fs": "^0.0.1-security",
41 | "html-webpack-plugin": "^5.3.1",
42 | "style-loader": "^2.0.0",
43 | "webpack": "^5.37.0",
44 | "webpack-cli": "^4.7.0",
45 | "webpack-dev-server": "^3.11.2"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/frontend/app/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | var webpack = require('webpack');
3 | const HtmlWebPackPlugin = require('html-webpack-plugin');
4 |
5 | module.exports = {
6 | output: {
7 | path: path.resolve(__dirname, 'dist'),
8 | filename: 'bundle.js',
9 | },
10 | resolve: {
11 | modules: [path.join(__dirname, 'src'), 'node_modules'],
12 | alias: {
13 | react: path.join(__dirname, 'node_modules', 'react'),
14 | },
15 | fallback: {
16 | "stream": require.resolve("stream-browserify"),
17 | "crypto": require.resolve("crypto-browserify"),
18 | "buffer": require.resolve("buffer/"),
19 | "util": require.resolve("util/")
20 | }
21 | },
22 | module: {
23 | rules: [
24 | {
25 | test: /\.(js|jsx)$/,
26 | exclude: /node_modules/,
27 | use: {
28 | loader: 'babel-loader',
29 | },
30 | },
31 | {
32 | test: /\.css$/,
33 | use: [
34 | {
35 | loader: 'style-loader',
36 | },
37 | {
38 | loader: 'css-loader',
39 | },
40 | ],
41 | },
42 | ],
43 | },
44 | plugins: [
45 | new HtmlWebPackPlugin({
46 | template: './src/index.html',
47 | favicon: './assets/frappe.png'
48 | }),
49 | new webpack.ProvidePlugin({
50 | process: 'process/browser',
51 | }),
52 | new webpack.EnvironmentPlugin({
53 | "process.env" : JSON.stringify(process.env),
54 | }),
55 | ],
56 | };
--------------------------------------------------------------------------------
/java/com/task/TaskServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.task;
2 |
3 | import static java.util.stream.Collectors.toList;
4 |
5 | import java.util.Collections;
6 | import java.lang.Integer;
7 | import java.util.List;
8 | import java.util.Optional;
9 | import java.util.stream.Stream;
10 | import java.util.concurrent.Callable;
11 | import java.util.concurrent.Executor;
12 | import com.google.inject.Inject;
13 | import com.google.rpc.Status;
14 | import com.google.common.base.Function;
15 | import com.google.common.util.concurrent.AsyncFunction;
16 | import com.google.common.util.concurrent.Futures;
17 | import com.google.common.util.concurrent.ListenableFuture;
18 | import com.google.common.util.concurrent.MoreExecutors;
19 | import io.grpc.protobuf.StatusProto;
20 | import com.google.rpc.Code;
21 | import io.grpc.stub.StreamObserver;
22 | import com.task.TaskServiceProto.ActionRequest;
23 | import com.task.TaskServiceProto.ActionResponse;
24 |
25 | public class TaskServiceImpl extends TaskServiceGrpc.TaskServiceImplBase {
26 |
27 | @Override
28 | public void someAction(ActionRequest req, StreamObserver responseObserver) {
29 |
30 | //get userid from context : Constants.USER_ID_CTX_KEY.get()
31 |
32 | responseObserver.onNext(
33 | ActionResponse.newBuilder()
34 | .addResultData("result data 1")
35 | .addResultData("result data 2")
36 | .addResultData("result data 3")
37 | .build());
38 |
39 | responseObserver.onCompleted();
40 |
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/frontend/app/webpack.dev.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebPackPlugin = require('html-webpack-plugin');
3 | var webpack = require('webpack');
4 |
5 | module.exports = {
6 | mode: 'production',
7 | output: {
8 | path: path.resolve(__dirname, 'devDist'),
9 | filename: '[name].bundle.js',
10 | },
11 | resolve: {
12 | modules: [
13 | path.join(process.cwd(), 'external/frapp_modules/node_modules'),
14 | ],
15 | fallback: {
16 | "stream": require.resolve("stream-browserify"),
17 | "crypto": require.resolve("crypto-browserify"),
18 | "buffer": require.resolve("buffer/"),
19 | "util": require.resolve("util/")
20 | }
21 | },
22 | entry : {
23 | index: [path.join(process.cwd(), "frontend/app/src/index.js")],
24 | },
25 | module: {
26 | rules: [
27 | {
28 | test: /\.(js|jsx)$/,
29 | exclude: /node_modules/,
30 | use: {
31 | loader: 'babel-loader',
32 | },
33 | },
34 | {
35 | test: /\.css$/,
36 | use: [
37 | {
38 | loader: 'style-loader',
39 | },
40 | {
41 | loader: 'css-loader',
42 | },
43 | ],
44 | },
45 | ],
46 | },
47 | plugins: [
48 | new HtmlWebPackPlugin({
49 | template: './frontend/app/src/index.html',
50 | }),
51 | new webpack.ProvidePlugin({
52 | process: 'process/browser',
53 | }),
54 | new webpack.EnvironmentPlugin({
55 | "process.env" : JSON.stringify(process.env),
56 | }),
57 | ],
58 | };
--------------------------------------------------------------------------------
/java/com/user/util/MainSSOValidator.java:
--------------------------------------------------------------------------------
1 | package com.user.util;
2 |
3 | import java.util.Collections;
4 | import java.util.Optional;
5 | import com.user.UserProto.User;
6 | import com.user.UserProto.UserId;
7 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
8 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken.Payload;
9 | import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
10 | import com.google.api.client.http.javanet.NetHttpTransport;
11 | import com.google.api.client.json.gson.GsonFactory;
12 |
13 | public class MainSSOValidator implements SSOValidator {
14 |
15 | @Override
16 | public Optional validateGoogleIdToken(String idToken){
17 |
18 | try{
19 | GoogleIdTokenVerifier verifier =
20 | new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new GsonFactory())
21 | .setAudience(Collections.singletonList(System.getenv("GOOGLE_CLIENT_ID")))
22 | .build();
23 | GoogleIdToken googleIdToken = verifier.verify(idToken);
24 |
25 | if (idToken != null) {
26 | Payload payload = googleIdToken.getPayload();
27 |
28 | System.out.println(payload);
29 |
30 | return Optional.of(User.newBuilder()
31 | .setGoogleUserId((String) payload.getSubject())
32 | .setName((String) payload.get("name"))
33 | .setEmail(payload.getEmail())
34 | .setProfileUrl((String) payload.get("picture"))
35 | .setLocale((String) payload.get("locale"))
36 | .build());
37 | }
38 | }
39 | catch(Exception e){
40 | }
41 | return Optional.empty();
42 | }
43 | }
--------------------------------------------------------------------------------
/frontend/app/webpack.prod.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const HtmlWebPackPlugin = require('html-webpack-plugin');
3 | var webpack = require('webpack');
4 |
5 | module.exports = {
6 | mode: 'production',
7 | output: {
8 | path: path.resolve(__dirname, 'dist'),
9 | filename: '[name].bundle.js',
10 | publicPath: '/frapp'
11 | },
12 | resolve: {
13 | modules: [
14 | path.join(process.cwd(), 'external/frapp_modules/node_modules'),
15 | ],
16 | fallback: {
17 | "stream": require.resolve("stream-browserify"),
18 | "crypto": require.resolve("crypto-browserify"),
19 | "buffer": require.resolve("buffer/"),
20 | "util": require.resolve("util/")
21 | }
22 | },
23 | entry : {
24 | index: [path.join(process.cwd(), "frontend/app/src/index.js")],
25 | },
26 | module: {
27 | rules: [
28 | {
29 | test: /\.(js|jsx)$/,
30 | exclude: /node_modules/,
31 | use: {
32 | loader: 'babel-loader',
33 | },
34 | },
35 | {
36 | test: /\.css$/,
37 | use: [
38 | {
39 | loader: 'style-loader',
40 | },
41 | {
42 | loader: 'css-loader',
43 | },
44 | ],
45 | },
46 | ],
47 | },
48 | plugins: [
49 | new HtmlWebPackPlugin({
50 | template: './frontend/app/src/index.html',
51 | favicon: './frontend/app/assets/frappe.png'
52 | }),
53 | new webpack.ProvidePlugin({
54 | process: 'process/browser',
55 | }),
56 | new webpack.DefinePlugin({
57 | "process.env" : JSON.stringify(process.env),
58 | }),
59 | ],
60 | };
--------------------------------------------------------------------------------
/frontend/next_app/README.md:
--------------------------------------------------------------------------------
1 | # Next.js + Tailwind CSS Example
2 |
3 | This example shows how to use [Tailwind CSS](https://tailwindcss.com/) [(v2.2)](https://blog.tailwindcss.com/tailwindcss-2-2) with Next.js. It follows the steps outlined in the official [Tailwind docs](https://tailwindcss.com/docs/guides/nextjs).
4 |
5 | It uses the new [`Just-in-Time Mode`](https://tailwindcss.com/docs/just-in-time-mode) for Tailwind CSS.
6 |
7 | ## Preview
8 |
9 | Preview the example live on [StackBlitz](http://stackblitz.com/):
10 |
11 | [](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/with-tailwindcss)
12 |
13 | ## Deploy your own
14 |
15 | Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
16 |
17 | [](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/with-tailwindcss&project-name=with-tailwindcss&repository-name=with-tailwindcss)
18 |
19 | ## How to use
20 |
21 | Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
22 |
23 | ```bash
24 | npx create-next-app --example with-tailwindcss with-tailwindcss-app
25 | # or
26 | yarn create next-app --example with-tailwindcss with-tailwindcss-app
27 | ```
28 |
29 | Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
30 |
--------------------------------------------------------------------------------
/frontend/app/src/HelloWorld.js:
--------------------------------------------------------------------------------
1 | import React, {useState, useEffect} from 'react';
2 | import Button from '@material-ui/core/Button';
3 | import {getUrl, getTaskServiceProto, getTaskServiceClient} from './protoHelper';
4 | import useAuthData from './hooks/useAuthData'
5 | import {useNavigate} from 'react-router-dom'
6 |
7 | const taskServiceProto = getTaskServiceProto()
8 | const client = getTaskServiceClient();
9 |
10 |
11 | const HelloWorld = () => {
12 |
13 | const [dataList, setDataList] = useState([]);
14 | const [loadingState, accessToken] = useAuthData();
15 | const navigate = useNavigate();
16 |
17 | useEffect(() => {
18 |
19 | });
20 |
21 | function getData(){
22 | var request = new taskServiceProto.ActionRequest();
23 |
24 | client.someAction(request, {"slot-a-tkn": accessToken},(err, data) => {
25 | if(err){
26 | console.log(err)
27 | return
28 | }
29 | console.log("testgrpc response")
30 | setDataList(data.getResultDataList())
31 | })
32 | }
33 |
34 | function getDataList(){
35 | return dataList.map(data => {data}
)
36 | }
37 |
38 | return (
39 |
40 |
React App - Private and within user session
41 |
46 |
47 |
50 |
51 |
52 | {getDataList()}
53 |
54 |
55 | );
56 | };
57 |
58 | export default HelloWorld;
--------------------------------------------------------------------------------
/frontend/next_app/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image")
4 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
5 | load("@io_bazel_rules_docker//container:container.bzl", "container_image")
6 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_web_library")
7 |
8 | NPM_PACKAGES = [
9 | "@frapp_next_modules//:node_modules",
10 | ]
11 |
12 | filegroup(
13 | name = "srcs",
14 | srcs = glob(["pages/**"])
15 | + glob(["components/**"])
16 | + glob(["util/**"])
17 | + glob(["public/**"])
18 | )
19 |
20 |
21 | nodejs_binary(
22 | name = "next",
23 | data = NPM_PACKAGES,
24 | entry_point = "@frapp_next_modules//:node_modules/next/dist/bin/next",
25 | )
26 |
27 | genrule(
28 | name = "prod_build",
29 | tools = [":next"],
30 | srcs = NPM_PACKAGES + [
31 | ":srcs",
32 | "next.config.js",
33 | "postcss.config.js",
34 | "tailwind.config.js",
35 | ],
36 | cmd = " && ".join([
37 | "cp -a frontend/next_app/ ./",
38 | "$(location next) build",
39 | "mkdir prodBuild",
40 | "mv external/frapp_next_modules/node_modules nextBuild public/ next.config.js tailwind.config.js prodBuild",
41 | "mv prodBuild $@",
42 | ]),
43 | outs = [
44 | "prodBuild",
45 | ],
46 | )
47 |
48 |
49 | container_image(
50 | name = "prod_image",
51 | base = "@node_base//image",
52 | files = [
53 | ":prod_build",
54 | "entry.sh",
55 | ],
56 | cmd = "".join([
57 | "ls;",
58 | "chmod +x ./entry.sh;",
59 | "./entry.sh;",
60 | "cd prodBuild/;",
61 | "ls;",
62 | "node_modules/next/dist/bin/next start;"
63 | ]),
64 | )
65 |
--------------------------------------------------------------------------------
/frontend/app/src/hooks/useAuthData.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import {gatewayUrl} from '../util/url'
3 | import axios from 'axios';
4 |
5 | var jwt = require("jsonwebtoken");
6 |
7 | export default function useAuthData(test = null){
8 |
9 | const [accessToken, setAccessToken] = useState(null);
10 |
11 | // IN_PROGRESS, SUCCESS, FAILED
12 | const [loadingState, setLoadingState] = useState(null);
13 |
14 |
15 | console.log()
16 | // after initial render set loading state to IP
17 | useEffect(() => {
18 | console.log("useAuth initial")
19 | setLoadingState("IN_PROGRESS");
20 | },[])
21 |
22 | useEffect(() => {
23 | if(loadingState === 'IN_PROGRESS'){
24 | console.log("useAuth fetch")
25 | console.log(gatewayUrl())
26 | // make call to fetch new access token
27 | axios.post(gatewayUrl()+"/gateway/refresh")
28 | .then(res => {
29 | console.log(res.data)
30 | if(res.data.access_token != null){
31 | setAccessToken(res.data.access_token);
32 | }else{
33 | setLoadingState("FAILED")
34 | }
35 | })
36 | .catch(function (error){
37 | setLoadingState("FAILED")
38 | })
39 | }
40 | }, [loadingState])
41 |
42 | useEffect(() =>{
43 | console.log("useAuth ac")
44 | console.log(accessToken)
45 | if(accessToken != null){
46 | console.log("useAuth set access")
47 | setLoadingState("SUCCESS");
48 | console.log((jwt.decode(accessToken)['iat'] - new Date().getTime()/1000 - 200) * 10)
49 | // parse access token iat, refetch 200 millis before expiration
50 | setTimeout(
51 | (jwt.decode(accessToken)['iat'] - new Date().getTime()/1000 - 200) * 100
52 | ,() => setLoadingState("IN_PROGRESS"))
53 | }
54 | }, [accessToken])
55 |
56 | return [loadingState, accessToken];
57 | }
--------------------------------------------------------------------------------
/java/com/task/BUILD:
--------------------------------------------------------------------------------
1 | load("@rules_java//java:defs.bzl", "java_library")
2 | load("@io_bazel_rules_docker//java:image.bzl", "java_image")
3 |
4 | package(default_visibility = ["//visibility:public"])
5 |
6 |
7 | java_library(
8 | name = "constants",
9 | srcs = ["Constants.java"],
10 | deps = [
11 | "@io_grpc_grpc_java//context",
12 | "@io_grpc_grpc_java//api",
13 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos",
14 | "//proto/user:user_java_proto",
15 | ],
16 | )
17 |
18 | java_library(
19 | name = "authenticated_interceptor",
20 | srcs = ["AuthenticatedInterceptor.java"],
21 | deps = [
22 | ":constants",
23 | "@io_grpc_grpc_java//api",
24 | "@io_grpc_grpc_java//context",
25 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos",
26 | "@com_google_protobuf_javalite//:protobuf_javalite",
27 | "//java/com/user/util:jwt_util",
28 | "//proto/user:user_java_proto",
29 | ],
30 | )
31 |
32 | java_library(
33 | name = "task_service_impl",
34 | srcs = ["TaskServiceImpl.java"],
35 | deps = [
36 | "@maven2//:com_google_inject_guice",
37 | "@io_grpc_grpc_java//api",
38 | "@com_google_api_grpc_proto_google_common_protos//:com_google_api_grpc_proto_google_common_protos",
39 | ":constants",
40 | "//proto/task:task_service_java_proto",
41 | ],
42 | )
43 |
44 |
45 | java_image(
46 | name = "task_java_image",
47 | srcs = ["TaskService.java"],
48 | deps = [
49 | "@maven2//:com_google_inject_guice",
50 | "@io_grpc_grpc_java//api",
51 | ":task_service_impl",
52 | ":authenticated_interceptor",
53 | "//java/com/util:service_module",
54 | "//java/com/util:setup-util",
55 | ],
56 | main_class = "com.task.TaskService",
57 | )
--------------------------------------------------------------------------------
/frontend/next_app/pages/api/auth/[...nextauth].js:
--------------------------------------------------------------------------------
1 | import NextAuth from "next-auth"
2 | import GoogleProvider from "next-auth/providers/google"
3 | import axios from 'axios';
4 | import Cookies from 'cookies';
5 | import {getUrl, omitAuthCalls} from '../../../util/protoHelper'
6 |
7 |
8 | const Auth = (req, res) => {
9 | const cookies = new Cookies(req, res)
10 |
11 | return (
12 | NextAuth(req, res, {
13 | site: process.env.NEXTAUTH_URL,
14 | providers: [
15 | GoogleProvider({
16 | clientId: (process.env.GOOGLE_CLIENT_ID ? process.env.GOOGLE_CLIENT_ID : "PH_GOOGLE_CLIENT_ID" ) ,
17 | clientSecret:(process.env.GOOGLE_CLIENT_SECRET ? process.env.GOOGLE_CLIENT_SECRET : "PH_GOOGLE_CLIENT_SECRET" )
18 | }),
19 | // ...add more providers here
20 | ],
21 | callbacks: {
22 | async signIn({ user, account, profile, email, credentials }) {
23 | console.log('sign in start')
24 | console.log(getUrl())
25 | if(!omitAuthCalls()){
26 | return axios.post(getUrl()+"/gateway/signIn", {"id_token": account.id_token})
27 | .then(res => {
28 | cookies.set("slot_refresh", res.data.refresh_token, {httpOnly: true})
29 | return true})
30 | .catch(err => {console.log(err); return false})
31 | }else{
32 | return true
33 | }
34 | },
35 | async session({ session, user, token }) {
36 | console.log("session ")
37 | console.log(session)
38 | session.user.picture = token.picture
39 | return session
40 | },
41 | async jwt({ token, user, account, profile, isNewUser }) {
42 | console.log("jwt ")
43 | console.log(token)
44 | return token
45 | }
46 | },
47 | secret : "kV1i6y9v+bWLAcZeqKapTPQGjh6VjgGP8pa8RBvilpQ=",
48 | }))
49 | }
50 |
51 | export default Auth
--------------------------------------------------------------------------------
/java/com/user/util/JWTUtil.java:
--------------------------------------------------------------------------------
1 | package com.user.util;
2 |
3 | import java.util.Optional;
4 | import java.util.Date;
5 | import io.jsonwebtoken.JwtParser;
6 | import io.jsonwebtoken.Jws;
7 | import io.jsonwebtoken.Claims;
8 | import io.jsonwebtoken.Jwts;
9 | import io.jsonwebtoken.SignatureAlgorithm;
10 | import com.user.UserProto.UserId;
11 | import com.user.UserProto.UserRefreshToken;
12 | import com.user.UserProto.UserAccessToken;
13 |
14 | public class JWTUtil{
15 |
16 | public final static String REFRESH_SIGNING_KEY = "refresh_slot_secret_value";
17 | public final static String ACCESS_SIGNING_KEY = "access_slot_secret_value";
18 |
19 | public static Optional validateRefreshToken(UserRefreshToken jwtToken){
20 | return parseJWTForUserId(jwtToken.getData(), REFRESH_SIGNING_KEY);
21 | }
22 |
23 | public static Optional validateAccessToken(UserAccessToken accessToken){
24 | return parseJWTForUserId(accessToken.getData(), ACCESS_SIGNING_KEY);
25 | }
26 |
27 | public static UserRefreshToken generateNewRefreshToken(UserId userId){
28 | return UserRefreshToken.newBuilder().setData(
29 | Jwts.builder()
30 | .setSubject(String.valueOf(userId.getId()))
31 | .setIssuedAt(new Date())
32 | .signWith(SignatureAlgorithm.HS256, REFRESH_SIGNING_KEY)
33 | .compact()
34 | ).build();
35 | }
36 |
37 | public static UserAccessToken generateNewAccessToken(UserId userId){
38 | return UserAccessToken.newBuilder().setData(
39 | Jwts.builder()
40 | .setSubject(String.valueOf(userId.getId()))
41 | .setIssuedAt(new Date())
42 | .signWith(SignatureAlgorithm.HS256, ACCESS_SIGNING_KEY)
43 | .compact()
44 | ).build();
45 | }
46 |
47 | private static Optional parseJWTForUserId(String token, String secret){
48 | JwtParser parser = Jwts.parser().setSigningKey(secret);
49 | Optional userId = Optional.empty();
50 |
51 | try {
52 | Jws claims = parser.parseClaimsJws(token.trim());
53 | userId = Optional.of(
54 | UserId.newBuilder().setId(
55 | Integer.parseInt(claims.getBody().getSubject())
56 | ).build());
57 | } catch (Exception e) {}
58 |
59 | return userId;
60 |
61 | }
62 | }
--------------------------------------------------------------------------------
/frontend/envoy/envoy.yaml:
--------------------------------------------------------------------------------
1 | admin:
2 | access_log_path: /tmp/admin_access.log
3 | address:
4 | socket_address: { address: 0.0.0.0, port_value: 9901 }
5 |
6 | static_resources:
7 | listeners:
8 | - name: listener_0
9 | address:
10 | socket_address: { address: 0.0.0.0, port_value: 80 }
11 | filter_chains:
12 | - filters:
13 | - name: envoy.filters.network.http_connection_manager
14 | typed_config:
15 | "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
16 | codec_type: auto
17 | stat_prefix: ingress_http
18 | route_config:
19 | name: local_route
20 | virtual_hosts:
21 | - name: local_service
22 | domains: ["*"]
23 | request_headers_to_add: []
24 | routes:
25 | - match: { prefix: "/" }
26 | route:
27 | cluster: task_service
28 | timeout: 0s
29 | max_stream_duration:
30 | grpc_timeout_header_max: 0s
31 | cors:
32 | allow_origin_string_match:
33 | - prefix: "*"
34 | allow_methods: GET, PUT, DELETE, POST, OPTIONS
35 | allow_headers: keep-alive,user-agent,cache-control,content-type,content-transfer-encoding,custom-header-1,slot-a-tkn,x-accept-content-transfer-encoding,x-accept-response-streaming,x-user-agent,x-grpc-web,grpc-timeout,Cookie
36 | max_age: "1728000"
37 | allow_credentials: true
38 | expose_headers: slot-a-tkn,custom-header-1,grpc-status,grpc-message
39 | http_filters:
40 | - name: envoy.filters.http.grpc_web
41 | - name: envoy.filters.http.cors
42 | - name: envoy.filters.http.router
43 | clusters:
44 | - name: task_service
45 | connect_timeout: 0.25s
46 | type: logical_dns
47 | http2_protocol_options: {}
48 | lb_policy: round_robin
49 | load_assignment:
50 | cluster_name: cluster_0
51 | endpoints:
52 | - lb_endpoints:
53 | - endpoint:
54 | address:
55 | socket_address:
56 | address: task
57 | port_value: 80
--------------------------------------------------------------------------------
/java/com/task/AuthenticatedInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.task;
2 |
3 | import io.grpc.Status;
4 | import io.grpc.ForwardingServerCall.SimpleForwardingServerCall;
5 | import io.grpc.Metadata;
6 | import io.grpc.Context;
7 | import io.grpc.Contexts;
8 | import io.grpc.ServerCall;
9 | import io.grpc.ServerCallHandler;
10 | import io.grpc.ServerInterceptor;
11 | import io.grpc.StatusRuntimeException;
12 | import java.util.Optional;
13 | import java.util.Arrays;
14 | import com.user.UserProto.UserId;
15 | import com.user.UserProto.UserAccessToken;
16 | import com.user.util.JWTUtil;
17 | import com.google.protobuf.InvalidProtocolBufferException;
18 |
19 | /**
20 | * A interceptor to authenticate protected paths.
21 | */
22 | public class AuthenticatedInterceptor implements ServerInterceptor {
23 |
24 | @Override
25 | public ServerCall.Listener interceptCall(
26 | ServerCall call,
27 | final Metadata requestHeaders,
28 | ServerCallHandler next) {
29 |
30 | Context ctx = Context.current();
31 |
32 | // parse cookie for custom value
33 | String cookieValue = requestHeaders.get(Constants.COOKIE_SLOT_METADATA_KEY);
34 | if(cookieValue == null){
35 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Invalid cookie"));
36 | }
37 | Optional customHeaderValue =
38 | Arrays.asList(cookieValue.split(";"))
39 | .stream()
40 | .filter(cookie -> cookie.split("=")[0].trim().equals(Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY)).findAny();
41 |
42 | if(customHeaderValue.isPresent()){
43 | ctx = ctx.withValue(Constants.CUSTOM_HEADER_CTX_KEY, customHeaderValue.get().split("=")[1].trim());
44 | }
45 |
46 | System.out.println("intercept");
47 | System.out.println(requestHeaders.toString());
48 |
49 | //parse metadata for auth header
50 | String accessTokenData = requestHeaders.get(Constants.ACCESS_SLOT_METADATA_KEY);
51 |
52 | if(accessTokenData == null){
53 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Missing access token"));
54 | }
55 |
56 | Optional userId = JWTUtil.validateAccessToken(UserAccessToken.newBuilder().setData(accessTokenData).build());
57 | if(!userId.isPresent()){
58 | throw new StatusRuntimeException(Status.INVALID_ARGUMENT.withDescription("Invalid access token"));
59 | }
60 |
61 | ctx = ctx.withValue(Constants.USER_ID_CTX_KEY, userId.get());
62 | return Contexts.interceptCall(ctx, call, requestHeaders, next);
63 | }
64 |
65 | }
--------------------------------------------------------------------------------
/frontend/app/BUILD:
--------------------------------------------------------------------------------
1 | package(default_visibility = ["//visibility:public"])
2 |
3 | load("@frapp_modules//webpack:index.bzl", "webpack")
4 | load("@frapp_modules//http_server:index.bzl", "http_server")
5 | load("@io_bazel_rules_docker//nodejs:image.bzl", "nodejs_image")
6 | load("@build_bazel_rules_nodejs//:index.bzl", "nodejs_binary")
7 | load("@io_bazel_rules_docker//container:container.bzl", "container_image")
8 | load("@rules_proto_grpc//js:defs.bzl", "js_grpc_web_library")
9 |
10 | NPM_PACKAGES = [
11 | "@frapp_modules//:node_modules",
12 | ]
13 |
14 | js_grpc_web_library(
15 | name = "task_grpc_js",
16 | protos = [
17 | "//proto/task:task_service_proto",
18 | "//proto/user:user_proto"
19 | ],
20 | deps_repo = "@frapp_modules",
21 | )
22 |
23 | filegroup(
24 | name = "srcs",
25 | srcs = glob(["src/**"], exclude = [ "src/genProto/**" ] ) + glob(["assets/**"])
26 | )
27 |
28 |
29 | nodejs_binary(
30 | name = "webpack_cli",
31 | data = NPM_PACKAGES,
32 | entry_point = "@frapp_modules//:node_modules/webpack-cli/bin/cli.js",
33 | )
34 |
35 | genrule(
36 | name = "prod_build",
37 | tools = [":webpack_cli"],
38 | srcs = NPM_PACKAGES + [
39 | ":srcs",
40 | "src/index.html",
41 | "webpack.prod.js",
42 | ".babelrc",
43 | ":task_grpc_js"
44 | ],
45 | cmd = " && ".join([
46 | "cp -a bazel-out/darwin-fastbuild/bin/frontend/app/task_grpc_js_pb/ frontend/app/src/",
47 | #"ls -R frontend | grep ':$$' | sed -e 's/:$$//' -e 's/[^-][^\/]*\//--/g' -e 's/^/ /' -e 's/-/|/'",
48 | "$(execpath webpack_cli) --mode=production --config=$(location webpack.prod.js)",
49 | "mv frontend/app/dist $@",
50 | ]),
51 | outs = [
52 | "dist",
53 | ],
54 | )
55 |
56 | genrule(
57 | name = "dev_build",
58 | tools = [":webpack_cli"],
59 | srcs = NPM_PACKAGES + [
60 | ":srcs",
61 | "src/index.html",
62 | "webpack.dev.js",
63 | ".babelrc",
64 | ":task_grpc_js",
65 | ],
66 | cmd = " && ".join([
67 | "cp -a bazel-out/darwin-fastbuild/bin/frontend/app/task_grpc_js_pb/ frontend/app/src/",
68 | "$(execpath webpack_cli) --mode=development --config=$(location webpack.dev.js)",
69 | "mv frontend/app/devDist $@",
70 | ]),
71 | outs = [
72 | "devDist",
73 | ],
74 | )
75 |
76 | http_server(
77 | name = "prod_server",
78 | data = [
79 | ":prod_build"
80 | ],
81 | # args = ["frontend/app/dist/"],
82 | )
83 |
84 | http_server(
85 | name = "dev_server",
86 | data = [
87 | ":dev_build"
88 | ],
89 | args = ["frontend/app/devDist"],
90 | )
91 |
92 |
93 | container_image(
94 | name = "dev_image",
95 | base = "@nginx_base//image",
96 | files = [
97 | ":dev_build",
98 | "nginxDev.conf",
99 | "entry.sh",
100 | ],
101 | cmd = "".join([
102 | "chmod +x ./entry.sh;",
103 | "./entry.sh;",
104 | "nginx -c /nginxDev.conf;"
105 | ]),
106 | )
107 |
108 | container_image(
109 | name = "prod_image",
110 | base = "@nginx_base//image",
111 | files = [
112 | ":prod_build",
113 | "nginx.conf",
114 | "entry.sh",
115 | ],
116 | cmd = "".join([
117 | "chmod +x ./entry.sh;",
118 | "./entry.sh;",
119 | "nginx -c /nginx.conf;"
120 | ]),
121 | )
122 |
--------------------------------------------------------------------------------
/nodejs/gateway.js:
--------------------------------------------------------------------------------
1 | const grpc = require('@grpc/grpc-js');
2 | const express = require('express')
3 | const cookieParser = require('cookie-parser');
4 | var bodyParser = require('body-parser')
5 |
6 | const taskServiceProto = require('./task_service_proto_pb/proto/task/task_service_grpc_pb.js')
7 | const taskProto = require('./task_service_proto_pb/proto/task/task_service_pb.js')
8 |
9 | const userProto = require('./user_js_proto_pb/proto/user/user_pb.js')
10 | const userServiceProto = require('./user_management_service_proto_pb/proto/user/user_management_service_pb.js')
11 | const userServiceClientProto = require('./user_management_service_proto_pb/proto/user/user_management_service_grpc_pb.js')
12 |
13 |
14 | // create application/json parser
15 | var jsonParser = bodyParser.json()
16 |
17 | const app = express()
18 | const port = 3000
19 | const REFRESH_COOKIE_KEY = "slot_refresh"
20 | const SITE_COOKIES = [
21 | REFRESH_COOKIE_KEY,
22 | "next-auth.session-token",
23 | "next-auth.callback-url",
24 | "next-auth.csrf-token",
25 | ]
26 |
27 |
28 | var client = new taskServiceProto.TaskServiceClient("task:80", grpc.credentials.createInsecure());
29 | var userClient = new userServiceClientProto.UserManagementServiceClient("user-management:80", grpc.credentials.createInsecure());
30 |
31 | app.use(cookieParser());
32 |
33 | app.get('/', (req, res) => {
34 | res.send('this is the GATEWAY!')
35 | })
36 |
37 | var actionRequest = () => new taskProto.ActionRequest();
38 |
39 | var refreshRequest = (existingRefreshToken) =>
40 | new userServiceProto.RegenerateRefreshTokenRequest()
41 | .setExistingRefreshToken(new userProto.UserRefreshToken().setData(existingRefreshToken));
42 |
43 | var signInRequest = (idToken) =>
44 | new userServiceProto.SignInRequest()
45 | .setIdToken(idToken);
46 |
47 |
48 | app.get('/testgrpc', (req, res) => {
49 | var request = actionRequest()
50 | client.someAction(request, (err, data) => {
51 | if(err){
52 | res.json({err: err})
53 | return
54 | }
55 | res.cookie('custom_slot_header', 'fromTestGrpc',{httpOnly:true})
56 | res.json({data : data.toObject()})
57 | })
58 | })
59 |
60 | app.post('/refresh', (req, res) => {
61 |
62 | console.log("refresh")
63 | console.log(req.cookies)
64 | if(req.cookies[REFRESH_COOKIE_KEY] == null){
65 | res.json({err:"Invalid refresh"})
66 | console.log('invalid refresh')
67 | return
68 | }
69 |
70 | userClient.regenerateRefreshToken(refreshRequest(req.cookies[REFRESH_COOKIE_KEY]), (err, data) => {
71 | if(err){
72 | res.json({err: err})
73 | return
74 | }
75 | res.cookie(REFRESH_COOKIE_KEY, data.getRefreshToken().getData(),{httpOnly:true});
76 | res.json({access_token: data.getAccessToken().getData()})
77 | })
78 | })
79 |
80 | app.post('/signIn',jsonParser, (req, res) => {
81 |
82 | console.log("signin")
83 |
84 | if(req.body.id_token == null){
85 | res.json({err:"Invalid Id Token"})
86 | return
87 | }
88 |
89 | userClient.signIn(signInRequest(req.body.id_token), (err, data) => {
90 | if(err){
91 | res.json({err: err})
92 | return
93 | }
94 | res.json({
95 | access_token: data.getAccessToken().getData(),
96 | refresh_token:data.getRefreshToken().getData()})
97 | })
98 | })
99 |
100 | app.get('/signOut', (req, res) => {
101 | SITE_COOKIES.forEach(cook => res.clearCookie(cook))
102 | res.redirect(200, process.env.REACT_APP_BASE_URL);
103 | })
104 |
105 | app.listen(port, () => {
106 | console.log("gateway server running")
107 | })
108 |
109 |
110 | function getRand(min, max) {
111 | return Math.trunc(Math.random() * (max - min) + min);
112 | }
113 |
114 |
--------------------------------------------------------------------------------
/java/com/user/db/FakeUserDBHandler.java:
--------------------------------------------------------------------------------
1 | package com.user.db;
2 |
3 | import java.util.Optional;
4 | import java.util.Map;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import com.google.common.util.concurrent.ListenableFuture;
8 | import com.google.common.util.concurrent.Futures;
9 | import com.user.UserDBProto.CreateUserRequest;
10 | import com.user.UserDBProto.CreateUserResponse;
11 | import com.user.UserDBProto.DBFetchUserRequest;
12 | import com.user.UserDBProto.DBFetchUserRequest.FetchableFields;
13 | import com.user.UserDBProto.DBFetchUserResponse;
14 | import com.user.UserProto.UserRefreshToken;
15 | import com.user.UserProto.UserId;
16 | import com.user.UserProto.User;
17 | import com.user.UserDBProto.UpdateRefreshTokenRequest;
18 | import com.user.UserDBProto.UpdateRefreshTokenResponse;
19 | import com.google.inject.Singleton;
20 |
21 | @Singleton
22 | public class FakeUserDBHandler implements UserDBHandler {
23 |
24 | private int DEFAULT_CREATED_USER_ID = 999;
25 |
26 | private Map refreshTokens = new HashMap();
27 | private Map emailToUser = new HashMap();
28 | private Optional createdUser = Optional.empty();
29 | private int createdUserId = DEFAULT_CREATED_USER_ID;
30 |
31 | @Override
32 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest){
33 |
34 | DBFetchUserResponse.Builder response = DBFetchUserResponse.newBuilder();
35 |
36 | if(refreshTokens.containsKey(fetchUserRequest.getUserId()) && fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_REFRESH_TOKEN)){
37 | response.setRefreshToken(refreshTokens.get(fetchUserRequest.getUserId()));
38 | }else if(emailToUser.containsKey(fetchUserRequest.getEmail()) && fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_USER)){
39 | response.setUser(emailToUser.get(fetchUserRequest.getEmail()));
40 | }
41 |
42 | return Futures.immediateFuture(response.build());
43 | }
44 |
45 | @Override
46 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest){
47 | System.out.println("fake updateRefreshToken");
48 | System.out.println(updateRefreshTokenRequest);
49 | refreshTokens.put(updateRefreshTokenRequest.getUserId(), updateRefreshTokenRequest.getNewRefreshToken());
50 |
51 | return Futures.immediateFuture(
52 | UpdateRefreshTokenResponse.newBuilder()
53 | .setNewRefreshToken(updateRefreshTokenRequest.getNewRefreshToken())
54 | .build());
55 |
56 | }
57 |
58 | @Override
59 | public ListenableFuture createUser(CreateUserRequest createUserRequest){
60 | UserId newUserId = UserId.newBuilder()
61 | .setId(createdUserId).build();
62 | createdUser =
63 | Optional.of(createUserRequest.getUser().toBuilder()
64 | .setUserId(newUserId).build());
65 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setCreatedUserId(newUserId).build());
66 | }
67 |
68 |
69 | public void clear(){
70 | refreshTokens = new HashMap();
71 | emailToUser = new HashMap();
72 | createdUser = Optional.empty();
73 | createdUserId = DEFAULT_CREATED_USER_ID;
74 | }
75 |
76 | public void setEmailToUser(String email, User user){
77 | emailToUser.put(email, user);
78 | }
79 |
80 | public void setRefreshTokenForUserId(UserId userId, UserRefreshToken refreshToken){
81 | refreshTokens.put(userId, refreshToken);
82 | }
83 |
84 | public UserRefreshToken getRefreshTokenForUserId(UserId userId){
85 | return refreshTokens.get(userId);
86 | }
87 |
88 | public Optional getCreatedUser(){
89 | return createdUser;
90 | }
91 |
92 | public void setCreatedUserId(int createdUserId){
93 | this.createdUserId = createdUserId;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/nodejs/.bazelrc:
--------------------------------------------------------------------------------
1 | # Common Bazel settings for JavaScript/NodeJS workspaces
2 | # This rc file is automatically discovered when Bazel is run in this workspace,
3 | # see https://docs.bazel.build/versions/master/guide.html#bazelrc
4 | #
5 | # The full list of Bazel options: https://docs.bazel.build/versions/master/command-line-reference.html
6 |
7 | # Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches)
8 | build --disk_cache=~/.cache/bazel-disk-cache
9 |
10 | # Bazel will create symlinks from the workspace directory to output artifacts.
11 | # Build results will be placed in a directory called "dist/bin"
12 | # Other directories will be created like "dist/testlogs"
13 | # Be aware that this will still create a bazel-out symlink in
14 | # your project directory, which you must exclude from version control and your
15 | # editor's search path.
16 | build --symlink_prefix=dist/
17 | # To disable the symlinks altogether (including bazel-out) you can use
18 | # build --symlink_prefix=/
19 | # however this makes it harder to find outputs.
20 |
21 | # Specifies desired output mode for running tests.
22 | # Valid values are
23 | # 'summary' to output only test status summary
24 | # 'errors' to also print test logs for failed tests
25 | # 'all' to print logs for all tests
26 | # 'streamed' to output logs for all tests in real time
27 | # (this will force tests to be executed locally one at a time regardless of --test_strategy value).
28 | test --test_output=errors
29 |
30 | # Support for debugging NodeJS tests
31 | # Add the Bazel option `--config=debug` to enable this
32 | # --test_output=streamed
33 | # Stream stdout/stderr output from each test in real-time.
34 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_output for more details.
35 | # --test_strategy=exclusive
36 | # Run one test at a time.
37 | # --test_timeout=9999
38 | # Prevent long running tests from timing out
39 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--test_timeout for more details.
40 | # --nocache_test_results
41 | # Always run tests
42 | # --node_options=--inspect-brk
43 | # Pass the --inspect-brk option to all tests which enables the node inspector agent.
44 | # See https://nodejs.org/de/docs/guides/debugging-getting-started/#command-line-options for more details.
45 | # --define=VERBOSE_LOGS=1
46 | # Rules will output verbose logs if the VERBOSE_LOGS environment variable is set. `VERBOSE_LOGS` will be passed to
47 | # `nodejs_binary` and `nodejs_test` via the default value of the `default_env_vars` attribute of those rules.
48 | # --compilation_mode=dbg
49 | # Rules may change their build outputs if the compilation mode is set to dbg. For example,
50 | # mininfiers such as terser may make their output more human readable when this is set. Rules will pass `COMPILATION_MODE`
51 | # to `nodejs_binary` executables via the actions.run env attribute.
52 | # See https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode for more details.
53 | test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --define=VERBOSE_LOGS=1
54 | # Use bazel run with `--config=debug` to turn on the NodeJS inspector agent.
55 | # The node process will break before user code starts and wait for the debugger to connect.
56 | run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk
57 | # The following option will change the build output of certain rules such as terser and may not be desirable in all cases
58 | build:debug --compilation_mode=dbg
59 |
60 | # Turn off legacy external runfiles
61 | # This prevents accidentally depending on this feature, which Bazel will remove.
62 | build --nolegacy_external_runfiles
63 |
64 | # Turn on --incompatible_strict_action_env which was on by default
65 | # in Bazel 0.21.0 but turned off again in 0.22.0. Follow
66 | # https://github.com/bazelbuild/bazel/issues/7026 for more details.
67 | # This flag is needed to so that the bazel cache is not invalidated
68 | # when running bazel via `yarn bazel`.
69 | # See https://github.com/angular/angular/issues/27514.
70 | build --incompatible_strict_action_env
71 | run --incompatible_strict_action_env
72 |
73 | # When running `bazel coverage` --instrument_test_targets needs to be set in order to
74 | # collect coverage information from test targets
75 | coverage --instrument_test_targets
76 |
77 | # Load any settings specific to the current user.
78 | # .bazelrc.user should appear in .gitignore so that settings are not shared with team members
79 | # This needs to be last statement in this
80 | # config, as the user configuration should be able to overwrite flags from this file.
81 | # See https://docs.bazel.build/versions/master/best-practices.html#bazelrc
82 | # (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing,
83 | # rather than user.bazelrc as suggested in the Bazel docs)
84 | try-import %workspace%/.bazelrc.user
85 |
86 |
--------------------------------------------------------------------------------
/spec_local/BUILD:
--------------------------------------------------------------------------------
1 | load("@io_bazel_rules_k8s//k8s:object.bzl", "k8s_object")
2 | load("@io_bazel_rules_k8s//k8s:objects.bzl", "k8s_objects")
3 |
4 | k8s_object(
5 | name = "deployment_gateway",
6 | kind = "deployment",
7 | kubeconfig = ":config",
8 | cluster = "minikube",
9 | template = ":deployment_gateway.yaml",
10 | substitutions = {
11 | "{SUB_BASE_URL}": "$(DOMAIN)",
12 | },
13 | images = {
14 | "YOUR_REGISTRY_NAME/slot_gateway": "//nodejs:nodejs_gateway_image"
15 | }
16 | )
17 |
18 | k8s_object(
19 | name = "deployment_task",
20 | kind = "deployment",
21 | kubeconfig = ":config",
22 | cluster = "minikube",
23 | template = ":deployment_task.yaml",
24 | images = {
25 | "YOUR_REGISTRY_NAME/slot_task": "//java/com/task:task_java_image"
26 | },
27 | )
28 |
29 | k8s_object(
30 | name = "deployment_user_management",
31 | kind = "deployment",
32 | kubeconfig = ":config",
33 | cluster = "minikube",
34 | template = ":deployment_user_management.yaml",
35 | substitutions = {
36 | "{GOOGLE_CLIENT_ID}": "$(GOOGLE_CLIENT_ID)",
37 | "{MONGODB_URI}": "$(MONGODB_URI)",
38 | "{MONGODB_DB}": "$(MONGODB_DB)",
39 | },
40 | images = {
41 | "YOUR_REGISTRY_NAME/slot_user_management": "//java/com/user/management:user_management_java_image"
42 | },
43 | )
44 |
45 | k8s_object(
46 | name = "deployment_frapp",
47 | kind = "deployment",
48 | kubeconfig = ":config",
49 | cluster = "minikube",
50 | template = ":deployment_frapp.yaml",
51 | substitutions = {
52 | "{SUB_BASE_URL}": "$(DOMAIN)",
53 | },
54 | images = {
55 | "YOUR_REGISTRY_NAME/slot_frapp": "//frontend/app:prod_image"
56 | },
57 | )
58 |
59 | k8s_object(
60 | name = "deployment_next_frapp",
61 | kind = "deployment",
62 | kubeconfig = ":config",
63 | cluster = "minikube",
64 | template = ":deployment_next_frapp.yaml",
65 | substitutions = {
66 | "{SUB_BASE_URL}": "$(CLUSTER_INGRESS_IP)",
67 | "{NEXTAUTH_URL}" : "$(DOMAIN)",
68 | "{GOOGLE_CLIENT_ID}": "$(GOOGLE_CLIENT_ID)",
69 | "{GOOGLE_CLIENT_SECRET}": "$(GOOGLE_CLIENT_SECRET)",
70 | },
71 | images = {
72 | "YOUR_REGISTRY_NAME/slot_next_frapp": "//frontend/next_app:prod_image"
73 | },
74 | )
75 |
76 | k8s_object(
77 | name = "deployment_envoy",
78 | kind = "deployment",
79 | kubeconfig = ":config",
80 | cluster = "minikube",
81 | template = ":deployment_envoy.yaml",
82 | images = {
83 | "YOUR_REGISTRY_NAME/slot_envoy": "//frontend/envoy:envoy_image"
84 | },
85 | )
86 |
87 | k8s_object(
88 | name = "deployment_mongodb",
89 | kind = "deployment",
90 | kubeconfig = ":config",
91 | cluster = "minikube",
92 | template = ":deployment_mongodb.yaml",
93 | images = {
94 | "YOUR_REGISTRY_NAME/slot_mongodb": "//local:db_local_image"
95 | },
96 | )
97 |
98 | k8s_object(
99 | name = "service_gateway",
100 | template = ":service_gateway.yaml",
101 | kubeconfig = ":config",
102 | cluster = "minikube",
103 | kind = "service"
104 | )
105 |
106 | k8s_object(
107 | name = "service_task",
108 | template = ":service_task.yaml",
109 | kubeconfig = ":config",
110 | cluster = "minikube",
111 | kind = "service"
112 | )
113 |
114 | k8s_object(
115 | name = "service_user_management",
116 | template = ":service_user_management.yaml",
117 | kubeconfig = ":config",
118 | cluster = "minikube",
119 | kind = "service"
120 | )
121 |
122 |
123 | k8s_object(
124 | name = "service_frapp",
125 | template = ":service_frapp.yaml",
126 | kubeconfig = ":config",
127 | cluster = "minikube",
128 | kind = "service"
129 | )
130 |
131 | k8s_object(
132 | name = "service_next_frapp",
133 | template = ":service_next_frapp.yaml",
134 | kubeconfig = ":config",
135 | cluster = "minikube",
136 | kind = "service"
137 | )
138 |
139 | k8s_object(
140 | name = "service_envoy",
141 | template = ":service_envoy.yaml",
142 | kubeconfig = ":config",
143 | cluster = "minikube",
144 | kind = "service"
145 | )
146 |
147 | k8s_object(
148 | name = "service_mongodb",
149 | template = ":service_mongodb.yaml",
150 | kubeconfig = ":config",
151 | cluster = "minikube",
152 | kind = "service"
153 | )
154 |
155 | k8s_object(
156 | name = "ingress",
157 | template = ":ingress.yaml",
158 | kubeconfig = ":config",
159 | cluster = "minikube",
160 | kind = "ingress"
161 | )
162 |
163 | k8s_object(
164 | name = "ns_slot",
165 | template = ":ns_slot.yaml",
166 | kubeconfig = ":config",
167 | cluster = "minikube",
168 | kind = "namespace"
169 | )
170 |
171 | k8s_objects(
172 | name = "non_nodejs_deployment",
173 | objects = [
174 | ":deployment_task",
175 | ":deployment_envoy",
176 | "deployment_mongodb",
177 | ":deployment_frapp",
178 | ":deployment_next_frapp",
179 | ]
180 | )
181 |
182 | k8s_objects(
183 | name = "nodejs_deployment",
184 | objects = [
185 | ":deployment_gateway",
186 | ]
187 | )
188 |
189 | k8s_objects(
190 | name = "services",
191 | objects = [
192 | ":service_gateway",
193 | ":service_task",
194 | ":service_user_management",
195 | ":service_frapp",
196 | ":service_next_frapp",
197 | ":service_envoy",
198 | ":service_mongodb"
199 | ]
200 | )
201 |
202 | k8s_objects(
203 | name = "k8s_1",
204 | objects = [
205 | ":ns_slot",
206 | ":non_nodejs_deployment",
207 | ":services",
208 | ":ingress"
209 | ]
210 | )
211 |
212 | k8s_objects(
213 | name = "k8s_2",
214 | objects = [
215 | ":deployment_user_management",
216 | ]
217 | )
--------------------------------------------------------------------------------
/java/test/com/task/TaskServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.task;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.jupiter.api.Assertions.assertThrows;
5 |
6 | import java.util.ArrayList;
7 | import org.junit.Test;
8 | import org.junit.Before;
9 | import org.junit.After;
10 | import org.junit.Rule;
11 | import org.junit.runner.RunWith;
12 | import org.junit.runners.JUnit4;
13 | import io.grpc.ManagedChannel;
14 | import io.grpc.testing.GrpcCleanupRule;
15 | import io.grpc.StatusRuntimeException;
16 | import io.grpc.inprocess.InProcessServerBuilder;
17 | import io.grpc.inprocess.InProcessChannelBuilder;
18 | import com.google.inject.Inject;
19 | import com.google.inject.Injector;
20 | import com.google.inject.Guice;
21 | import com.google.common.collect.Iterables;
22 | import com.task.TaskServiceGrpc;
23 | import com.user.util.JWTUtil;
24 | import com.task.TaskServiceProto.ActionRequest;
25 | import com.task.TaskServiceProto.ActionResponse;
26 | import com.task.TaskServiceImpl;
27 | import com.task.Constants;
28 | import com.task.AuthenticatedInterceptor;
29 | import io.grpc.Metadata;
30 | import io.grpc.stub.MetadataUtils;
31 | import com.user.UserProto.UserId;
32 | import com.util.FakeServiceModule;
33 |
34 | @RunWith(JUnit4.class)
35 | public class TaskServiceTest {
36 |
37 | private final UserId USER_ID = UserId.newBuilder().setId(11111).build();
38 | private final String COOKIE_VALUE = "TEST1233";
39 |
40 | protected Injector injector = Guice.createInjector(new FakeServiceModule());
41 |
42 | @Rule
43 | public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
44 |
45 | private String serverName = InProcessServerBuilder.generateName();
46 | private InProcessServerBuilder serverBuilder = InProcessServerBuilder
47 | .forName(serverName).directExecutor();
48 | private InProcessChannelBuilder channelBuilder = InProcessChannelBuilder
49 | .forName(serverName).directExecutor();
50 |
51 | private Metadata metadata;
52 |
53 | @Before
54 | public void setup() {
55 | injector.injectMembers(this);
56 |
57 | //setup metadata
58 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "="+ COOKIE_VALUE;
59 | metadata = new Metadata();
60 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue);
61 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, JWTUtil.generateNewAccessToken(USER_ID).getData());
62 | }
63 |
64 | @After
65 | public void cleanUp() {
66 | }
67 |
68 | @Test
69 | public void shouldReturnOneRecurringGeneratedEntry() throws Exception {
70 |
71 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub();
72 | ActionResponse response =
73 | blockingStub.someAction(ActionRequest.getDefaultInstance());
74 |
75 | assertEquals(response.getResultData(0), "result data 1");
76 | }
77 |
78 |
79 | // Authenticate Tests
80 | @Test
81 | public void shouldSetCustomAndUserIdHeader() throws Exception {
82 |
83 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233";
84 | metadata = new Metadata();
85 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue);
86 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, JWTUtil.generateNewAccessToken(USER_ID).getData());
87 |
88 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub();
89 |
90 | ActionResponse response =
91 | blockingStub.someAction(ActionRequest.getDefaultInstance());
92 |
93 | assertEquals(response.getResultData(0), "result data 1");
94 | }
95 |
96 | @Test
97 | public void shouldThrow_missingAccesstoken() throws Exception {
98 |
99 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233";
100 | metadata = new Metadata();
101 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue);
102 |
103 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub();
104 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
105 | () -> blockingStub.someAction(ActionRequest.getDefaultInstance()));
106 |
107 | assertEquals(exception.getStatus().getDescription(), "Missing access token");
108 | }
109 |
110 | @Test
111 | public void shouldThrow_invalidAccesstoken() throws Exception {
112 |
113 | String cookieValue = Constants.CUSTOM_SLOT_HEADER_COOKIE_KEY + "=TEST1233";
114 | metadata = new Metadata();
115 | metadata.put(Constants.COOKIE_SLOT_METADATA_KEY, cookieValue);
116 | String accessToken = "fdsf";
117 | metadata.put(Constants.ACCESS_SLOT_METADATA_KEY, accessToken);
118 |
119 | TaskServiceGrpc.TaskServiceBlockingStub blockingStub = createBlockingStub();
120 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
121 | () -> blockingStub.someAction(ActionRequest.getDefaultInstance()));
122 |
123 | assertEquals(exception.getStatus().getDescription(), "Invalid access token");
124 | }
125 |
126 | private TaskServiceGrpc.TaskServiceBlockingStub createBlockingStub() throws Exception{
127 | // Add the service to the in-process server.
128 | grpcCleanup.register(
129 | serverBuilder.addService(injector.getInstance(TaskServiceImpl.class)).intercept(new AuthenticatedInterceptor()).build().start());
130 | ManagedChannel channel = grpcCleanup.register(
131 | channelBuilder.maxInboundMessageSize(1024).build());
132 |
133 | return MetadataUtils.attachHeaders(TaskServiceGrpc.newBlockingStub(channel),metadata);
134 |
135 | }
136 |
137 |
138 | }
--------------------------------------------------------------------------------
/java/com/user/db/MainUserDBHandler.java:
--------------------------------------------------------------------------------
1 | package com.user.db;
2 |
3 | import static com.mongodb.client.model.Filters.eq;
4 | import static com.mongodb.client.model.Sorts.ascending;
5 |
6 | import java.util.Optional;
7 | import java.util.List;
8 | import java.util.Map;
9 | import java.util.Arrays;
10 | import java.util.HashMap;
11 | import com.google.common.util.concurrent.ListenableFuture;
12 | import com.google.common.util.concurrent.Futures;
13 | import com.user.UserDBProto.CreateUserRequest;
14 | import com.user.UserDBProto.CreateUserResponse;
15 | import com.user.UserDBProto.DBFetchUserRequest;
16 | import com.user.UserDBProto.DBFetchUserRequest.FetchableFields;
17 | import com.user.UserDBProto.DBFetchUserResponse;
18 | import com.user.UserProto.UserRefreshToken;
19 | import com.user.UserProto.UserId;
20 | import com.user.UserProto.User;
21 | import com.user.UserDBProto.UpdateRefreshTokenRequest;
22 | import com.user.UserDBProto.UpdateRefreshTokenResponse;
23 | import com.mongodb.client.MongoClient;
24 | import com.mongodb.client.MongoClients;
25 | import com.mongodb.client.MongoCursor;
26 | import com.mongodb.client.MongoCollection;
27 | import com.mongodb.client.MongoDatabase;
28 | import com.mongodb.client.model.UpdateOptions;
29 | import com.mongodb.client.model.Updates;
30 | import com.mongodb.client.result.UpdateResult;
31 | import com.mongodb.MongoException;
32 | import org.bson.Document;
33 | import org.bson.conversions.Bson;
34 |
35 | public class MainUserDBHandler implements UserDBHandler {
36 |
37 | private final String COLLECTION_REFRESH = "refresh";
38 | private final String COLLECTION_USERS = "users";
39 | MongoClient mongoClient;
40 |
41 | public MainUserDBHandler(){
42 | mongoClient = MongoClients.create("mongodb://"+System.getenv("MONGODB_URI")+":80");
43 |
44 | }
45 |
46 | @Override
47 | public ListenableFuture fetchUser(DBFetchUserRequest fetchUserRequest){
48 |
49 | System.out.println("fetchUser");
50 | System.out.println(fetchUserRequest);
51 | //TODO: add is-set checks for fetchable fields
52 | boolean isRefreshRequest = fetchUserRequest.getFieldList().contains(FetchableFields.USER_FIELD_REFRESH_TOKEN);
53 |
54 | DBFetchUserResponse.Builder builder = DBFetchUserResponse.newBuilder();
55 | MongoCollection collection = getCollection(isRefreshRequest ? COLLECTION_REFRESH : COLLECTION_USERS);
56 |
57 | Document result;
58 | if(fetchUserRequest.hasUserId()){
59 | result = collection.find(eq("user_id", fetchUserRequest.getUserId().getId()))
60 | .first();
61 | }else{
62 | result = collection.find(eq("email", fetchUserRequest.getEmail()))
63 | .first();
64 | }
65 |
66 | System.out.println(result);
67 |
68 | if(result != null){
69 | if(isRefreshRequest){
70 | builder.setRefreshToken(UserRefreshToken.newBuilder().setData(result.get("token", String.class)).build());
71 | }else{
72 | builder.setUser(constructUser(result));
73 | }
74 | }
75 |
76 | return Futures.immediateFuture(builder.build());
77 | }
78 |
79 | @Override
80 | public ListenableFuture updateRefreshToken(UpdateRefreshTokenRequest updateRefreshTokenRequest){
81 |
82 | Document query = new Document().append("user_id", updateRefreshTokenRequest.getUserId().getId());
83 | Bson updates = Updates.combine(
84 | Updates.set("token", updateRefreshTokenRequest.getNewRefreshToken().getData()));
85 | UpdateOptions options = new UpdateOptions().upsert(true);
86 |
87 | try {
88 | getCollection(COLLECTION_REFRESH).updateOne(query, updates, options);
89 | return Futures.immediateFuture(
90 | UpdateRefreshTokenResponse.newBuilder()
91 | .setNewRefreshToken(updateRefreshTokenRequest.getNewRefreshToken())
92 | .build());
93 |
94 | } catch (MongoException me) {
95 | return Futures.immediateFuture(UpdateRefreshTokenResponse.getDefaultInstance());
96 | }
97 | }
98 |
99 | @Override
100 | public ListenableFuture createUser(CreateUserRequest createUserRequest){
101 |
102 | try {
103 |
104 | MongoCollection collection = getCollection(COLLECTION_USERS);
105 | MongoCursor result = collection.find().sort(ascending("user_id")).limit(1).iterator();
106 | System.out.println("createUser");
107 |
108 | int createdUserId = (result.hasNext() ? result.next().get("user_id",Double.class).intValue() + 1 : 101);
109 |
110 | System.out.println(createdUserId);
111 |
112 | getCollection(COLLECTION_USERS).insertOne(
113 | new Document()
114 | .append("user_id", createdUserId)
115 | .append("name", createUserRequest.getUser().getName())
116 | .append("email", createUserRequest.getUser().getEmail())
117 | .append("google_user_id", createUserRequest.getUser().getGoogleUserId()));
118 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setCreatedUserId(UserId.newBuilder().setId(createdUserId).build()).build());
119 | } catch (Exception e) {
120 | System.out.println(e);
121 | return Futures.immediateFuture(CreateUserResponse.newBuilder().setError(CreateUserResponse.Error.CREATION_ERROR_UNKOWN).build());
122 | }
123 |
124 | }
125 |
126 | private User constructUser(Document result){
127 | return User.newBuilder()
128 | .setUserId(
129 | UserId.newBuilder()
130 | .setId(result.get("user_id",Integer.class))
131 | .build())
132 | .setName(result.get("name", String.class))
133 | .setEmail(result.get("email", String.class))
134 | .build();
135 | }
136 |
137 | private MongoCollection getCollection(String collection){
138 | MongoDatabase database = mongoClient.getDatabase(System.getenv("MONGODB_DB"));
139 | return database.getCollection(collection);
140 | }
141 |
142 |
143 | }
144 |
--------------------------------------------------------------------------------
/WORKSPACE:
--------------------------------------------------------------------------------
1 | workspace(name = "slot" ,
2 | # Map the @npm bazel workspace to the node_modules directory.
3 | # This lets Bazel use the same node_modules as other local tooling.
4 | managed_directories = {
5 | "@nodejs_modules": ["nodejs/node_modules"],
6 | "@frapp_modules": ["frontend/app/node_modules"],
7 | "@frapp_next_modules": ["frontend/next_app/node_modules"],
8 | })
9 |
10 | # functions to get external libs
11 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
12 |
13 | load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
14 | http_archive(
15 | name = "rules_java",
16 | url = "https://github.com/bazelbuild/rules_java/releases/download/4.0.0/rules_java-4.0.0.tar.gz",
17 | sha256 = "34b41ec683e67253043ab1a3d1e8b7c61e4e8edefbcad485381328c934d072fe",
18 | )
19 |
20 | load("@rules_java//java:repositories.bzl", "rules_java_dependencies", "rules_java_toolchains")
21 | rules_java_dependencies()
22 | rules_java_toolchains()
23 |
24 |
25 | # proto - grpc service ( should also get proto_rules dep )
26 | http_archive(
27 | name = "rules_proto_grpc",
28 | sha256 = "8383116d4c505e93fd58369841814acc3f25bdb906887a2023980d8f49a0b95b",
29 | strip_prefix = "rules_proto_grpc-4.1.0",
30 | urls = ["https://github.com/rules-proto-grpc/rules_proto_grpc/archive/4.1.0.tar.gz"],
31 | )
32 |
33 | load("@rules_proto_grpc//:repositories.bzl", "rules_proto_grpc_toolchains", "rules_proto_grpc_repos")
34 | rules_proto_grpc_toolchains()
35 | rules_proto_grpc_repos()
36 |
37 | load("@rules_proto_grpc//java:repositories.bzl", rules_proto_grpc_java_repos="java_repos")
38 | rules_proto_grpc_java_repos()
39 |
40 | load("@io_grpc_grpc_java//:repositories.bzl", "IO_GRPC_GRPC_JAVA_ARTIFACTS", "IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS", "grpc_java_repositories")
41 |
42 | grpc_java_repositories()
43 |
44 | # maven_install
45 | RULES_JVM_EXTERNAL_TAG = "4.0"
46 | RULES_JVM_EXTERNAL_SHA = "31701ad93dbfe544d597dbe62c9a1fdd76d81d8a9150c2bf1ecf928ecdf97169"
47 |
48 | http_archive(
49 | name = "rules_jvm_external",
50 | strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
51 | sha256 = RULES_JVM_EXTERNAL_SHA,
52 | url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
53 | )
54 |
55 | load("@rules_jvm_external//:defs.bzl", "maven_install")
56 |
57 |
58 | maven_install(
59 | artifacts = IO_GRPC_GRPC_JAVA_ARTIFACTS,
60 | generate_compat_repositories = True,
61 | override_targets = IO_GRPC_GRPC_JAVA_OVERRIDE_TARGETS,
62 | repositories = [
63 | "https://repo.maven.apache.org/maven2/",
64 | ],
65 | )
66 |
67 | load("@maven//:compat.bzl", "compat_repositories")
68 |
69 | compat_repositories()
70 |
71 | #guice
72 | #grpc testing
73 | grpc_version = "1.27.0"
74 | guice_version = "4.1.0"
75 | maven_install(
76 | name = "maven2",
77 | artifacts = [
78 | "io.grpc:grpc-testing:%s" % grpc_version,
79 | "com.google.guava:guava:30.0-jre",
80 | 'com.google.inject:guice:' + guice_version,
81 | 'org.junit.jupiter:junit-jupiter-api:5.8.1',
82 | "com.google.api-client:google-api-client:1.32.2",
83 | "org.mongodb:mongodb-driver-sync:4.4.0",
84 | "org.mongodb:bson:4.4.0",
85 | "org.mongodb:mongo-java-driver:3.12.7",
86 | "org.slf4j:slf4j-simple:1.7.32",
87 | ],
88 | repositories = [
89 | "https://jcenter.bintray.com/",
90 | "https://repo1.maven.org/maven2",
91 | "https://maven.google.com",
92 | ],
93 | )
94 |
95 | ## rules_docker
96 | http_archive(
97 | name = "io_bazel_rules_docker",
98 | sha256 = "59536e6ae64359b716ba9c46c39183403b01eabfbd57578e84398b4829ca499a",
99 | strip_prefix = "rules_docker-0.22.0",
100 | urls = ["https://github.com/bazelbuild/rules_docker/releases/download/v0.22.0/rules_docker-v0.22.0.tar.gz"],
101 | )
102 | load(
103 | "@io_bazel_rules_docker//repositories:repositories.bzl",
104 | container_repositories = "repositories",
105 | )
106 | container_repositories()
107 | load("@io_bazel_rules_docker//repositories:deps.bzl", container_deps = "deps")
108 | container_deps()
109 |
110 | # Load java_image rules to create java docker images to run grpc services
111 | load(
112 | "@io_bazel_rules_docker//java:image.bzl",
113 | _java_image_repos = "repositories",
114 | )
115 | _java_image_repos()
116 |
117 | # Load the macro that allows you to customize the docker toolchain configuration.
118 | load("@io_bazel_rules_docker//toolchains/docker:toolchain.bzl",
119 | docker_toolchain_configure="toolchain_configure"
120 | )
121 |
122 | docker_toolchain_configure(
123 | name = "docker_config",
124 | # abosolute path to credentials directory
125 | client_config="${ROOT}/credentials",
126 | docker_flags = [
127 | "--tls",
128 | "--log-level=error",
129 | ],
130 | )
131 |
132 | #java jsonwebtoken
133 |
134 | maven_install(
135 | name = "jjwt",
136 | artifacts = [
137 | "io.jsonwebtoken:jjwt:0.9.0",
138 | "javax.xml.bind:jaxb-api:2.3.1"
139 | ],
140 | repositories = [
141 | "https://jcenter.bintray.com/",
142 | "https://repo1.maven.org/maven2",
143 | "https://maven.google.com",
144 | ],
145 | )
146 |
147 | #nodejs & js rules - already installed from proto grpc
148 | load("@rules_proto_grpc//js:repositories.bzl", rules_proto_grpc_js_repos="js_repos")
149 | rules_proto_grpc_js_repos()
150 |
151 | #installing nodejs rules
152 | http_archive(
153 | name = "build_bazel_rules_nodejs",
154 | sha256 = "f0f76a06fd6c10e8fb9a6cb9389fa7d5816dbecd9b1685063f89fb20dc6822f3",
155 | urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/4.5.1/rules_nodejs-4.5.1.tar.gz"],
156 | )
157 |
158 | # The npm_install rule runs yarn anytime the package.json or package-lock.json file changes.
159 | # It also extracts any Bazel rules distributed in an npm package.
160 | load("@build_bazel_rules_nodejs//:index.bzl","yarn_install")
161 | yarn_install(
162 | # Name this npm so that Bazel Label references look like @npm//package
163 | name = "nodejs_modules",
164 | package_json = "//nodejs:package.json",
165 | yarn_lock = "//nodejs:yarn.lock",
166 | )
167 |
168 | yarn_install(
169 | # Name this npm so that Bazel Label references look like @npm//package
170 | name = "npm",
171 | package_json = "//npm:package.json",
172 | yarn_lock = "//npm:yarn.lock",
173 | )
174 |
175 | yarn_install(
176 | name = "frapp_modules",
177 | package_json = "//frontend/app:package.json",
178 | yarn_lock = "//frontend/app:yarn.lock"
179 | )
180 |
181 | yarn_install(
182 | name = "frapp_next_modules",
183 | package_json = "//frontend/next_app:package.json",
184 | yarn_lock = "//frontend/next_app:yarn.lock"
185 | )
186 |
187 | # Load nodejs_image rules to create java docker images to run grpc services
188 | load(
189 | "@io_bazel_rules_docker//nodejs:image.bzl",
190 | _nodejs_image_repos = "repositories",
191 | )
192 | _nodejs_image_repos()
193 |
194 |
195 | # nginx docker base image
196 | load("@io_bazel_rules_docker//container:pull.bzl", "container_pull")
197 |
198 | container_pull(
199 | name = "nginx_base",
200 | registry = "index.docker.io",
201 | repository = "library/nginx",
202 | tag = "1.21.0-alpine",
203 | )
204 |
205 | #envoy base image
206 | container_pull(
207 | name = "envoy_base",
208 | registry = "index.docker.io",
209 | repository = "envoyproxy/envoy",
210 | tag = "v1.18.3"
211 | )
212 |
213 | #node base image
214 | container_pull(
215 | name = "node_base",
216 | registry = "index.docker.io",
217 | repository = "library/node",
218 | tag = "alpine3.14"
219 | )
220 |
221 | #mongodb base image
222 | container_pull(
223 | name = "mongo_base",
224 | registry = "index.docker.io",
225 | repository = "library/mongo"
226 | )
227 |
228 | # k8s - push images directly to cluster
229 | http_archive(
230 | name = "io_bazel_rules_k8s",
231 | strip_prefix = "rules_k8s-0.6",
232 | urls = ["https://github.com/bazelbuild/rules_k8s/archive/v0.6.tar.gz"],
233 | sha256 = "51f0977294699cd547e139ceff2396c32588575588678d2054da167691a227ef",
234 | )
235 |
236 | load("@io_bazel_rules_k8s//k8s:k8s.bzl", "k8s_repositories")
237 |
238 | k8s_repositories()
239 |
240 | load("@io_bazel_rules_k8s//k8s:k8s_go_deps.bzl", k8s_go_deps = "deps")
241 |
242 | k8s_go_deps()
243 |
--------------------------------------------------------------------------------
/java/test/com/user/management/UserManagementServiceTest.java:
--------------------------------------------------------------------------------
1 | package com.user.management;
2 |
3 | import static org.junit.Assert.assertEquals;
4 | import static org.junit.Assert.assertNotSame;
5 | import static org.junit.jupiter.api.Assertions.assertThrows;
6 |
7 | import java.util.concurrent.TimeUnit;
8 | import org.junit.Test;
9 | import org.junit.Before;
10 | import org.junit.After;
11 | import org.junit.Rule;
12 | import org.junit.runner.RunWith;
13 | import org.junit.runners.JUnit4;
14 | import io.grpc.StatusRuntimeException;
15 | import io.grpc.ManagedChannel;
16 | import io.grpc.testing.GrpcCleanupRule;
17 | import io.grpc.inprocess.InProcessServerBuilder;
18 | import io.grpc.inprocess.InProcessChannelBuilder;
19 | import com.google.inject.Inject;
20 | import com.google.inject.Injector;
21 | import com.google.inject.Guice;
22 | import com.google.common.collect.Iterables;
23 | import com.user.management.UserManagementServiceGrpc;
24 | import com.user.UserProto.UserId;
25 | import com.user.UserProto.UserAccessToken;
26 | import com.user.UserProto.User;
27 | import com.user.UserProto.UserRefreshToken;
28 | import com.user.management.UserManagementServiceProto.SignInRequest;
29 | import com.user.management.UserManagementServiceProto.SignInResponse;
30 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenRequest;
31 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenResponse;
32 | import com.user.util.FakeSSOValidator;
33 | import com.user.db.FakeUserDBHandler;
34 | import com.util.FakeServiceModule;
35 | import com.user.util.JWTUtil;
36 | import com.user.management.UserManagementServiceImpl;
37 |
38 | /**
39 | * User Management Service Test
40 | */
41 | @RunWith(JUnit4.class)
42 | public class UserManagementServiceTest {
43 |
44 | private final String SAMPLE_SSO_ID_TOKEN = "sample-id-token";
45 | private final UserId USER_ID = UserId.newBuilder().setId(11111).build();
46 | private final String SAMPLE_USER_EMAIL = "smaple@user.com";
47 | private final User SAMPLE_USER =
48 | User.newBuilder()
49 | .setUserId(USER_ID)
50 | .setName("sample user")
51 | .setEmail(SAMPLE_USER_EMAIL)
52 | .build();
53 | private final User SAMPLE_USER_FROM_SSO =
54 | User.newBuilder()
55 | .setName("sample user sso")
56 | .setEmail(SAMPLE_USER_EMAIL)
57 | .build();
58 |
59 | private Injector injector = Guice.createInjector(new FakeServiceModule());
60 | private String serverName = InProcessServerBuilder.generateName();
61 | private InProcessServerBuilder serverBuilder = InProcessServerBuilder
62 | .forName(serverName).directExecutor();
63 | private InProcessChannelBuilder channelBuilder = InProcessChannelBuilder
64 | .forName(serverName).directExecutor();
65 |
66 | @Inject private FakeUserDBHandler fakeUserDBHandler;
67 | @Inject private FakeSSOValidator fakeSSOValidator;
68 |
69 | @Rule
70 | public GrpcCleanupRule grpcCleanup = new GrpcCleanupRule();
71 |
72 | @Before
73 | public void setup() {
74 | injector.injectMembers(this);
75 | }
76 |
77 | @After
78 | public void cleanUp() {
79 | fakeUserDBHandler.clear();
80 | }
81 |
82 | @Test
83 | public void shouldValidateExistingRefresh_generateAndReturnNewRefreshToken() throws Exception {
84 |
85 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID);
86 |
87 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken);
88 |
89 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub();
90 | RegenerateRefreshTokenResponse response =
91 | blockingStub.regenerateRefreshToken(
92 | RegenerateRefreshTokenRequest.newBuilder()
93 | .setExistingRefreshToken(existingRefreshToken)
94 | .build());
95 | // new on file refresh token should be same as returned
96 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(USER_ID), response.getRefreshToken());
97 | assertNotSame(response.getRefreshToken(), existingRefreshToken);
98 | validateAccessTokenToId(response.getAccessToken(), USER_ID.getId());
99 | }
100 |
101 | @Test
102 | public void shouldThrow_invalidRefreshToken_unparsable() throws Exception {
103 |
104 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID);
105 |
106 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken);
107 |
108 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub();
109 |
110 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
111 | () -> blockingStub.regenerateRefreshToken(
112 | RegenerateRefreshTokenRequest.newBuilder()
113 | .setExistingRefreshToken(UserRefreshToken.newBuilder().setData("test").build())
114 | .build()));
115 | assertEquals(exception.getStatus().getDescription(), "Invalid refresh token");
116 |
117 | }
118 |
119 | @Test
120 | public void shouldThrow_invalidRefreshToken_notOnFile() throws Exception {
121 |
122 | UserRefreshToken existingRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID);
123 | //wait a second s.t. issuedAt date is different per token
124 | TimeUnit.SECONDS.sleep(1);
125 | UserRefreshToken anotherRefreshToken = JWTUtil.generateNewRefreshToken(USER_ID);
126 |
127 | fakeUserDBHandler.setRefreshTokenForUserId(USER_ID, existingRefreshToken);
128 |
129 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub();
130 |
131 | StatusRuntimeException exception = assertThrows(StatusRuntimeException.class,
132 | () -> blockingStub.regenerateRefreshToken(
133 | RegenerateRefreshTokenRequest.newBuilder()
134 | .setExistingRefreshToken(anotherRefreshToken)
135 | .build()));
136 | assertEquals(exception.getStatus().getDescription(), "Invalid refresh token");
137 |
138 | }
139 |
140 | // Sign in
141 | @Test
142 | public void shouldLogin_validGoogleIdToken_existingUser() throws Exception {
143 | fakeUserDBHandler.setEmailToUser(SAMPLE_USER_EMAIL, SAMPLE_USER);
144 | fakeSSOValidator.setUserToIdToken(SAMPLE_SSO_ID_TOKEN, SAMPLE_USER_FROM_SSO);
145 |
146 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub();
147 | SignInResponse response =
148 | blockingStub.signIn(
149 | SignInRequest.newBuilder()
150 | .setIdToken(SAMPLE_SSO_ID_TOKEN)
151 | .build());
152 | // new on file refresh token should be same as returned
153 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(USER_ID), response.getRefreshToken());
154 | validateAccessTokenToId(response.getAccessToken(), USER_ID.getId());
155 | }
156 |
157 | @Test
158 | public void shouldLogin_validGoogleIdToken_newuser() throws Exception {
159 | fakeSSOValidator.setUserToIdToken(SAMPLE_SSO_ID_TOKEN, SAMPLE_USER_FROM_SSO);
160 | fakeUserDBHandler.setCreatedUserId(1099);
161 |
162 | UserManagementServiceGrpc.UserManagementServiceBlockingStub blockingStub = createBlockingStub();
163 | SignInResponse response =
164 | blockingStub.signIn(
165 | SignInRequest.newBuilder()
166 | .setIdToken(SAMPLE_SSO_ID_TOKEN)
167 | .build());
168 | // new on file refresh token should be same as returned
169 | assertEquals(fakeUserDBHandler.getRefreshTokenForUserId(UserId.newBuilder().setId(1099).build()), response.getRefreshToken());
170 | validateAccessTokenToId(response.getAccessToken(), 1099);
171 | assertEquals(fakeUserDBHandler.getCreatedUser().get(), SAMPLE_USER_FROM_SSO.toBuilder().setUserId(UserId.newBuilder().setId(1099).build()).build());
172 | }
173 |
174 | private void validateAccessTokenToId(UserAccessToken accessToken, int userId){
175 | assertEquals(JWTUtil.validateAccessToken(accessToken).get().getId(), userId);
176 | }
177 |
178 | private UserManagementServiceGrpc.UserManagementServiceBlockingStub createBlockingStub() throws Exception{
179 | // Add the service to the in-process server.
180 | grpcCleanup.register(
181 | serverBuilder.addService(injector.getInstance(UserManagementServiceImpl.class)).build().start());
182 | ManagedChannel channel = grpcCleanup.register(
183 | channelBuilder.maxInboundMessageSize(1024).build());
184 |
185 | return UserManagementServiceGrpc.newBlockingStub(channel);
186 |
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/java/com/user/management/UserManagementServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.user.management;
2 |
3 | import java.util.Optional;
4 | import java.util.concurrent.Executor;
5 | import com.google.common.util.concurrent.AsyncFunction;
6 | import com.google.common.util.concurrent.Futures;
7 | import com.google.common.util.concurrent.ListenableFuture;
8 | import com.google.common.util.concurrent.MoreExecutors;
9 | import com.google.inject.Inject;
10 | import io.grpc.stub.StreamObserver;
11 | import io.grpc.protobuf.StatusProto;
12 | import com.google.rpc.Status;
13 | import com.google.rpc.Code;
14 | import com.user.UserDBProto.DBFetchUserRequest;
15 | import com.user.UserDBProto.DBFetchUserResponse;
16 | import com.user.db.UserDBHandler;
17 | import com.user.UserProto.UserId;
18 | import com.user.UserProto.User;
19 | import com.user.UserProto.UserRefreshToken;
20 | import com.user.util.JWTUtil;
21 | import com.user.util.SSOValidator;
22 | import com.user.UserDBProto.UpdateRefreshTokenRequest;
23 | import com.user.UserDBProto.UpdateRefreshTokenResponse;
24 | import com.user.UserDBProto.CreateUserRequest;
25 | import com.user.UserDBProto.CreateUserResponse;
26 | import com.user.management.UserManagementServiceProto.SignInRequest;
27 | import com.user.management.UserManagementServiceProto.SignInResponse;
28 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenRequest;
29 | import com.user.management.UserManagementServiceProto.RegenerateRefreshTokenResponse;
30 |
31 |
32 | public class UserManagementServiceImpl extends UserManagementServiceGrpc.UserManagementServiceImplBase {
33 |
34 | private UserDBHandler userDBHandler;
35 | private SSOValidator ssoValidator;
36 |
37 | @Inject
38 | public UserManagementServiceImpl(
39 | UserDBHandler userDBHandler,
40 | SSOValidator ssoValidator){
41 | this.userDBHandler = userDBHandler;
42 | this.ssoValidator = ssoValidator;
43 | }
44 |
45 | @Override
46 | public void regenerateRefreshToken(RegenerateRefreshTokenRequest req, StreamObserver responseObserver) {
47 |
48 | Executor executor = MoreExecutors.newDirectExecutorService();
49 |
50 | if(!req.hasExistingRefreshToken()){
51 | invalidJwt(responseObserver);
52 | }else {
53 | UserRefreshToken existingRefreshToken = req.getExistingRefreshToken();
54 | Optional updateRefreshToken =
55 | internalRegenerateRefreshToken(
56 | existingRefreshToken,
57 | responseObserver,
58 | executor);
59 | if(updateRefreshToken.isPresent()){
60 | responseObserver.onNext(
61 | RegenerateRefreshTokenResponse.newBuilder()
62 | .setRefreshToken(
63 | updateRefreshToken.get())
64 | .setAccessToken(
65 | JWTUtil.generateNewAccessToken(
66 | JWTUtil.validateRefreshToken(existingRefreshToken).get()
67 | ))
68 | .build());
69 | responseObserver.onCompleted();
70 | }
71 | }
72 | }
73 |
74 | @Override
75 | public void signIn(SignInRequest req, StreamObserver responseObserver) {
76 |
77 | System.out.println("sign in ");
78 | System.out.println(req);
79 | Executor executor = MoreExecutors.newDirectExecutorService();
80 |
81 | /*
82 | 1) validate id token passed
83 | 2) fetch user for email
84 | - if new, create new user
85 | 3) gen refresh/access tokens
86 | */
87 |
88 | Optional userFromIdToken = ssoValidator.validateGoogleIdToken(req.getIdToken());
89 | System.out.println(userFromIdToken);
90 | if(userFromIdToken.isPresent()){
91 |
92 | try{
93 | ListenableFuture dbFetchUserFuture =
94 | userDBHandler.fetchUser(
95 | DBFetchUserRequest.newBuilder()
96 | .setEmail(userFromIdToken.get().getEmail())
97 | .addField(DBFetchUserRequest.FetchableFields.USER_FIELD_USER)
98 | .build());
99 |
100 | PotentialUserCreationResult potentialUserCreationResult = createNewUserIfNeededWithRefreshToken(
101 | dbFetchUserFuture,
102 | userFromIdToken.get(),
103 | executor).get();
104 |
105 | System.out.println("PotentialUserCreationResult");
106 | System.out.println(potentialUserCreationResult.getUserId());
107 | System.out.println(potentialUserCreationResult.getUserRefreshTokenFuture().get());
108 |
109 | responseObserver.onNext(
110 | SignInResponse.newBuilder()
111 | .setRefreshToken(
112 | potentialUserCreationResult.getUserRefreshTokenFuture().get())
113 | .setAccessToken(
114 | JWTUtil.generateNewAccessToken(
115 | potentialUserCreationResult.getUserId()
116 | ))
117 | .build());
118 | responseObserver.onCompleted();
119 |
120 | }catch(Exception e){
121 | Status status = Status.newBuilder()
122 | .setCode(Code.UNKNOWN.getNumber())
123 | .setMessage("something bad happened - pending")
124 | .build();
125 | responseObserver.onError(StatusProto.toStatusRuntimeException(status));
126 |
127 | }
128 |
129 | }else{
130 | // TODO: invalid id token throw
131 | }
132 |
133 | }
134 |
135 | private Optional internalRegenerateRefreshToken(
136 | UserRefreshToken existingRefreshToken,
137 | StreamObserver streamObserver,
138 | Executor executor ){
139 |
140 | /*
141 | 1) validate refresh token
142 | 2) retrieve user id from token
143 | 3) fetch stored token and compare tokens
144 | 4) generate new token, store in db and return
145 | */
146 | Optional userId = JWTUtil.validateRefreshToken(existingRefreshToken);
147 |
148 | if(!userId.isPresent()){
149 | invalidJwt(streamObserver);
150 | }else{
151 | try{
152 | Optional> updateRefreshTokenFuture =
153 | verifyRefreshTokenAndUpdate(
154 | existingRefreshToken,
155 | userId.get(),
156 | executor);
157 |
158 | if(!updateRefreshTokenFuture.isPresent()){
159 | invalidJwt(streamObserver);
160 | }else{
161 | return Optional.of(updateRefreshTokenFuture.get().get().getNewRefreshToken());
162 | }
163 | } catch(Exception e){
164 | Status status = Status.newBuilder()
165 | .setCode(Code.UNKNOWN.getNumber())
166 | .setMessage("something bad happened - pending")
167 | .build();
168 | streamObserver.onError(StatusProto.toStatusRuntimeException(status));
169 | }
170 | }
171 |
172 | return Optional.empty();
173 | }
174 |
175 | private Optional> verifyRefreshTokenAndUpdate(
176 | UserRefreshToken existingRefreshToken,
177 | UserId userId,
178 | Executor executor) throws Exception{
179 |
180 | ListenableFuture dbFetchUserFuture = userDBHandler.fetchUser(
181 | DBFetchUserRequest.newBuilder()
182 | .setUserId(userId)
183 | .addField(DBFetchUserRequest.FetchableFields.USER_FIELD_REFRESH_TOKEN)
184 | .build());
185 |
186 | return validateAndSetNewRefreshToken(
187 | dbFetchUserFuture,
188 | existingRefreshToken,
189 | userId,
190 | executor).get();
191 |
192 | }
193 |
194 | private ListenableFuture>> validateAndSetNewRefreshToken(
195 | ListenableFuture dbFetchUserFuture,
196 | UserRefreshToken existingRefreshToken,
197 | UserId userId,
198 | Executor executor){
199 | AsyncFunction>> setNewTokenFunction =
200 | new AsyncFunction>>() {
201 | public ListenableFuture>> apply(DBFetchUserResponse fetchUserResponse) {
202 | Optional> response = Optional.empty();
203 | if(fetchUserResponse.getRefreshToken().equals(existingRefreshToken)){
204 | response = Optional.of(updateRefreshTokenFuture(userId));
205 | }
206 | return Futures.immediateFuture(response);
207 | }
208 | };
209 | return Futures.transformAsync(dbFetchUserFuture, setNewTokenFunction, executor);
210 | }
211 |
212 | final class PotentialUserCreationResult{
213 | ListenableFuture userRefreshTokenFuture;
214 | UserId userId;
215 |
216 | public PotentialUserCreationResult(ListenableFuture userRefreshTokenFuture,UserId userId ){
217 | this.userRefreshTokenFuture = userRefreshTokenFuture;
218 | this.userId = userId;
219 | }
220 |
221 | public ListenableFuture getUserRefreshTokenFuture(){
222 | return userRefreshTokenFuture;
223 | }
224 |
225 | public UserId getUserId(){
226 | return userId;
227 | }
228 |
229 | }
230 |
231 | private ListenableFuture createNewUserIfNeededWithRefreshToken(
232 | ListenableFuture dbFetchUserFuture,
233 | User userFromIdToken,
234 | Executor executor){
235 |
236 | System.out.println("createNewUserIfNeededWithRefreshToken");
237 |
238 | AsyncFunction handleUpdateRefreshTokenFunction =
239 | new AsyncFunction() {
240 | public ListenableFuture apply(UpdateRefreshTokenResponse updateRefreshTokenResponse) {
241 | return Futures.immediateFuture(updateRefreshTokenResponse.getNewRefreshToken());
242 | }
243 | };
244 |
245 | AsyncFunction handleCreateUserFunction =
246 | new AsyncFunction() {
247 | public ListenableFuture apply(CreateUserResponse createUserResponse) {
248 | System.out.println("handleCreateUserFunction");
249 | System.out.println(createUserResponse);
250 | // TODO: handle errors in creation
251 | return Futures.immediateFuture(new PotentialUserCreationResult(
252 | Futures.transformAsync(
253 | updateRefreshTokenFuture(createUserResponse.getCreatedUserId()), handleUpdateRefreshTokenFunction, executor),
254 | createUserResponse.getCreatedUserId()));
255 | }
256 | };
257 |
258 | AsyncFunction handleFetchUserResponseFunction =
259 | new AsyncFunction() {
260 | public ListenableFuture apply(DBFetchUserResponse fetchUserResponse) {
261 | System.out.println("fetchUserResponse");
262 | System.out.println(fetchUserResponse);
263 | if(fetchUserResponse.hasUser()){
264 | // update refresh token for existing user
265 | return Futures.immediateFuture(
266 | new PotentialUserCreationResult(
267 | Futures.transformAsync(
268 | updateRefreshTokenFuture(fetchUserResponse.getUser().getUserId()), handleUpdateRefreshTokenFunction, executor),
269 | fetchUserResponse.getUser().getUserId()));
270 | }else{
271 | return Futures.transformAsync(
272 | createUser(userFromIdToken), handleCreateUserFunction, executor);
273 | }
274 | }
275 | };
276 | return Futures.transformAsync(dbFetchUserFuture, handleFetchUserResponseFunction, executor);
277 | }
278 |
279 | private ListenableFuture updateRefreshTokenFuture(UserId userId){
280 | return userDBHandler.updateRefreshToken(
281 | UpdateRefreshTokenRequest.newBuilder()
282 | .setUserId(userId)
283 | .setNewRefreshToken(JWTUtil.generateNewRefreshToken(userId))
284 | .build());
285 |
286 | }
287 |
288 | private ListenableFuture createUser(User user){
289 | return userDBHandler.createUser(
290 | CreateUserRequest.newBuilder()
291 | .setUser(user)
292 | .build());
293 |
294 | }
295 |
296 | private void invalidJwt(StreamObserver responseObserver){
297 | Status status = Status.newBuilder()
298 | .setCode(Code.INVALID_ARGUMENT.getNumber())
299 | .setMessage("Invalid refresh token")
300 | .build();
301 |
302 | responseObserver.onError(StatusProto.toStatusRuntimeException(status));
303 | }
304 |
305 |
306 |
307 | }
--------------------------------------------------------------------------------
/npm/yarn.lock:
--------------------------------------------------------------------------------
1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
2 | # yarn lockfile v1
3 |
4 |
5 | "@grpc/grpc-js@^1.3.4":
6 | version "1.3.4"
7 | resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.3.4.tgz#5c4f5df717cd10cc5ebbc7523504008d1ff7b322"
8 | integrity sha512-AxtZcm0mArQhY9z8T3TynCYVEaSKxNCa9mVhVwBCUnsuUEe8Zn94bPYYKVQSLt+hJJ1y0ukr3mUvtWfcATL/IQ==
9 | dependencies:
10 | "@types/node" ">=12.12.47"
11 |
12 | "@mapbox/node-pre-gyp@^1.0.5":
13 | version "1.0.5"
14 | resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950"
15 | integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA==
16 | dependencies:
17 | detect-libc "^1.0.3"
18 | https-proxy-agent "^5.0.0"
19 | make-dir "^3.1.0"
20 | node-fetch "^2.6.1"
21 | nopt "^5.0.0"
22 | npmlog "^4.1.2"
23 | rimraf "^3.0.2"
24 | semver "^7.3.4"
25 | tar "^6.1.0"
26 |
27 | "@types/node@>=12.12.47":
28 | version "16.0.0"
29 | resolved "https://registry.yarnpkg.com/@types/node/-/node-16.0.0.tgz#067a6c49dc7a5c2412a505628e26902ae967bf6f"
30 | integrity sha512-TmCW5HoZ2o2/z2EYi109jLqIaPIi9y/lc2LmDCWzuCi35bcaQ+OtUh6nwBiFK7SOu25FAU5+YKdqFZUwtqGSdg==
31 |
32 | abbrev@1:
33 | version "1.1.1"
34 | resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8"
35 | integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==
36 |
37 | agent-base@6:
38 | version "6.0.2"
39 | resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
40 | integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
41 | dependencies:
42 | debug "4"
43 |
44 | ansi-regex@^2.0.0:
45 | version "2.1.1"
46 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
47 | integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
48 |
49 | ansi-regex@^3.0.0:
50 | version "3.0.0"
51 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
52 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
53 |
54 | aproba@^1.0.3:
55 | version "1.2.0"
56 | resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
57 | integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
58 |
59 | are-we-there-yet@~1.1.2:
60 | version "1.1.5"
61 | resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21"
62 | integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==
63 | dependencies:
64 | delegates "^1.0.0"
65 | readable-stream "^2.0.6"
66 |
67 | balanced-match@^1.0.0:
68 | version "1.0.2"
69 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
70 | integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
71 |
72 | brace-expansion@^1.1.7:
73 | version "1.1.11"
74 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
75 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
76 | dependencies:
77 | balanced-match "^1.0.0"
78 | concat-map "0.0.1"
79 |
80 | chownr@^2.0.0:
81 | version "2.0.0"
82 | resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
83 | integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
84 |
85 | code-point-at@^1.0.0:
86 | version "1.1.0"
87 | resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
88 | integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
89 |
90 | concat-map@0.0.1:
91 | version "0.0.1"
92 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
93 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
94 |
95 | console-control-strings@^1.0.0, console-control-strings@~1.1.0:
96 | version "1.1.0"
97 | resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e"
98 | integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=
99 |
100 | core-util-is@~1.0.0:
101 | version "1.0.2"
102 | resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
103 | integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
104 |
105 | debug@4:
106 | version "4.3.1"
107 | resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee"
108 | integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==
109 | dependencies:
110 | ms "2.1.2"
111 |
112 | delegates@^1.0.0:
113 | version "1.0.0"
114 | resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
115 | integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
116 |
117 | detect-libc@^1.0.3:
118 | version "1.0.3"
119 | resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b"
120 | integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=
121 |
122 | fs-minipass@^2.0.0:
123 | version "2.1.0"
124 | resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
125 | integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
126 | dependencies:
127 | minipass "^3.0.0"
128 |
129 | fs.realpath@^1.0.0:
130 | version "1.0.0"
131 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
132 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
133 |
134 | gauge@~2.7.3:
135 | version "2.7.4"
136 | resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7"
137 | integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=
138 | dependencies:
139 | aproba "^1.0.3"
140 | console-control-strings "^1.0.0"
141 | has-unicode "^2.0.0"
142 | object-assign "^4.1.0"
143 | signal-exit "^3.0.0"
144 | string-width "^1.0.1"
145 | strip-ansi "^3.0.1"
146 | wide-align "^1.1.0"
147 |
148 | glob@^7.1.3:
149 | version "7.1.7"
150 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90"
151 | integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==
152 | dependencies:
153 | fs.realpath "^1.0.0"
154 | inflight "^1.0.4"
155 | inherits "2"
156 | minimatch "^3.0.4"
157 | once "^1.3.0"
158 | path-is-absolute "^1.0.0"
159 |
160 | google-protobuf@^3.15.5, google-protobuf@^3.17.3:
161 | version "3.17.3"
162 | resolved "https://registry.yarnpkg.com/google-protobuf/-/google-protobuf-3.17.3.tgz#f87595073545a77946c8f0b67c302c5f7646d700"
163 | integrity sha512-OVPzcSWIAJ+d5yiHyeaLrdufQtrvaBrF4JQg+z8ynTkbO3uFcujqXszTumqg1cGsAsjkWnI+M5B1xZ19yR4Wyg==
164 |
165 | grpc-tools@^1.11.2:
166 | version "1.11.2"
167 | resolved "https://registry.yarnpkg.com/grpc-tools/-/grpc-tools-1.11.2.tgz#22d802d40012510ccc6591d11f9c94109ac07aab"
168 | integrity sha512-4+EgpnnkJraamY++oyBCw5Hp9huRYfgakjNVKbiE3PgO9Tv5ydVlRo7ZyGJ0C0SEiA7HhbVc1sNNtIyK7FiEtg==
169 | dependencies:
170 | "@mapbox/node-pre-gyp" "^1.0.5"
171 |
172 | has-unicode@^2.0.0:
173 | version "2.0.1"
174 | resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
175 | integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=
176 |
177 | https-proxy-agent@^5.0.0:
178 | version "5.0.0"
179 | resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz#e2a90542abb68a762e0a0850f6c9edadfd8506b2"
180 | integrity sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==
181 | dependencies:
182 | agent-base "6"
183 | debug "4"
184 |
185 | inflight@^1.0.4:
186 | version "1.0.6"
187 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
188 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
189 | dependencies:
190 | once "^1.3.0"
191 | wrappy "1"
192 |
193 | inherits@2, inherits@~2.0.3:
194 | version "2.0.4"
195 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
196 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
197 |
198 | is-fullwidth-code-point@^1.0.0:
199 | version "1.0.0"
200 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb"
201 | integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs=
202 | dependencies:
203 | number-is-nan "^1.0.0"
204 |
205 | is-fullwidth-code-point@^2.0.0:
206 | version "2.0.0"
207 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f"
208 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=
209 |
210 | isarray@~1.0.0:
211 | version "1.0.0"
212 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
213 | integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
214 |
215 | lru-cache@^6.0.0:
216 | version "6.0.0"
217 | resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
218 | integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
219 | dependencies:
220 | yallist "^4.0.0"
221 |
222 | make-dir@^3.1.0:
223 | version "3.1.0"
224 | resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
225 | integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
226 | dependencies:
227 | semver "^6.0.0"
228 |
229 | minimatch@^3.0.4:
230 | version "3.0.4"
231 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
232 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
233 | dependencies:
234 | brace-expansion "^1.1.7"
235 |
236 | minipass@^3.0.0:
237 | version "3.1.3"
238 | resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
239 | integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
240 | dependencies:
241 | yallist "^4.0.0"
242 |
243 | minizlib@^2.1.1:
244 | version "2.1.2"
245 | resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
246 | integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
247 | dependencies:
248 | minipass "^3.0.0"
249 | yallist "^4.0.0"
250 |
251 | mkdirp@^1.0.3:
252 | version "1.0.4"
253 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
254 | integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
255 |
256 | ms@2.1.2:
257 | version "2.1.2"
258 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
259 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
260 |
261 | node-fetch@^2.6.1:
262 | version "2.6.1"
263 | resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
264 | integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
265 |
266 | nopt@^5.0.0:
267 | version "5.0.0"
268 | resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88"
269 | integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==
270 | dependencies:
271 | abbrev "1"
272 |
273 | npmlog@^4.1.2:
274 | version "4.1.2"
275 | resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b"
276 | integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==
277 | dependencies:
278 | are-we-there-yet "~1.1.2"
279 | console-control-strings "~1.1.0"
280 | gauge "~2.7.3"
281 | set-blocking "~2.0.0"
282 |
283 | number-is-nan@^1.0.0:
284 | version "1.0.1"
285 | resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
286 | integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
287 |
288 | object-assign@^4.1.0:
289 | version "4.1.1"
290 | resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
291 | integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
292 |
293 | once@^1.3.0:
294 | version "1.4.0"
295 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
296 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
297 | dependencies:
298 | wrappy "1"
299 |
300 | path-is-absolute@^1.0.0:
301 | version "1.0.1"
302 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
303 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
304 |
305 | process-nextick-args@~2.0.0:
306 | version "2.0.1"
307 | resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
308 | integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
309 |
310 | readable-stream@^2.0.6:
311 | version "2.3.7"
312 | resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57"
313 | integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==
314 | dependencies:
315 | core-util-is "~1.0.0"
316 | inherits "~2.0.3"
317 | isarray "~1.0.0"
318 | process-nextick-args "~2.0.0"
319 | safe-buffer "~5.1.1"
320 | string_decoder "~1.1.1"
321 | util-deprecate "~1.0.1"
322 |
323 | rimraf@^3.0.2:
324 | version "3.0.2"
325 | resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
326 | integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
327 | dependencies:
328 | glob "^7.1.3"
329 |
330 | safe-buffer@~5.1.0, safe-buffer@~5.1.1:
331 | version "5.1.2"
332 | resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
333 | integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
334 |
335 | semver@^6.0.0:
336 | version "6.3.0"
337 | resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
338 | integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
339 |
340 | semver@^7.3.4:
341 | version "7.3.5"
342 | resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.5.tgz#0b621c879348d8998e4b0e4be94b3f12e6018ef7"
343 | integrity sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==
344 | dependencies:
345 | lru-cache "^6.0.0"
346 |
347 | set-blocking@~2.0.0:
348 | version "2.0.0"
349 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
350 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
351 |
352 | signal-exit@^3.0.0:
353 | version "3.0.3"
354 | resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
355 | integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
356 |
357 | string-width@^1.0.1:
358 | version "1.0.2"
359 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
360 | integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=
361 | dependencies:
362 | code-point-at "^1.0.0"
363 | is-fullwidth-code-point "^1.0.0"
364 | strip-ansi "^3.0.0"
365 |
366 | "string-width@^1.0.2 || 2":
367 | version "2.1.1"
368 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
369 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
370 | dependencies:
371 | is-fullwidth-code-point "^2.0.0"
372 | strip-ansi "^4.0.0"
373 |
374 | string_decoder@~1.1.1:
375 | version "1.1.1"
376 | resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
377 | integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==
378 | dependencies:
379 | safe-buffer "~5.1.0"
380 |
381 | strip-ansi@^3.0.0, strip-ansi@^3.0.1:
382 | version "3.0.1"
383 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
384 | integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=
385 | dependencies:
386 | ansi-regex "^2.0.0"
387 |
388 | strip-ansi@^4.0.0:
389 | version "4.0.0"
390 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
391 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
392 | dependencies:
393 | ansi-regex "^3.0.0"
394 |
395 | tar@^6.1.0:
396 | version "6.1.0"
397 | resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
398 | integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
399 | dependencies:
400 | chownr "^2.0.0"
401 | fs-minipass "^2.0.0"
402 | minipass "^3.0.0"
403 | minizlib "^2.1.1"
404 | mkdirp "^1.0.3"
405 | yallist "^4.0.0"
406 |
407 | ts-protoc-gen@^0.15.0:
408 | version "0.15.0"
409 | resolved "https://registry.yarnpkg.com/ts-protoc-gen/-/ts-protoc-gen-0.15.0.tgz#2fec5930b46def7dcc9fa73c060d770b7b076b7b"
410 | integrity sha512-TycnzEyrdVDlATJ3bWFTtra3SCiEP0W0vySXReAuEygXCUr1j2uaVyL0DhzjwuUdQoW5oXPwk6oZWeA0955V+g==
411 | dependencies:
412 | google-protobuf "^3.15.5"
413 |
414 | util-deprecate@~1.0.1:
415 | version "1.0.2"
416 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
417 | integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
418 |
419 | wide-align@^1.1.0:
420 | version "1.1.3"
421 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457"
422 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==
423 | dependencies:
424 | string-width "^1.0.2 || 2"
425 |
426 | wrappy@1:
427 | version "1.0.2"
428 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
429 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
430 |
431 | yallist@^4.0.0:
432 | version "4.0.0"
433 | resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
434 | integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
435 |
--------------------------------------------------------------------------------