From addb92a0753d4576c9b8328148bcc2ffc993b21e Mon Sep 17 00:00:00 2001 From: Hackerwins Date: Tue, 2 Aug 2022 17:26:05 +0900 Subject: [PATCH] Add auth interceptors for admin client --- package.json | 4 +- src/App.tsx | 30 +- src/api/admin.proto | 21 + src/api/admin_grpc_web_pb.d.ts | 24 ++ src/api/admin_grpc_web_pb.js | 122 ++++++ src/api/admin_pb.d.ts | 82 ++++ src/api/admin_pb.js | 689 ++++++++++++++++++++++++++++++ src/api/auth_interceptor.ts | 79 ++++ src/api/converter.ts | 23 +- src/api/index.ts | 76 ++-- src/api/resources.proto | 603 +++++++++++++------------- src/api/resources_pb.d.ts | 28 ++ src/api/resources_pb.js | 233 ++++++++++ src/api/types.ts | 3 +- src/features/users/LoginForm.tsx | 12 +- src/features/users/SignupForm.tsx | 11 +- src/features/users/usersSlice.ts | 57 ++- src/routes/PrivateRoute.tsx | 17 + src/routes/index.ts | 8 +- 19 files changed, 1744 insertions(+), 378 deletions(-) create mode 100644 src/api/auth_interceptor.ts create mode 100644 src/routes/PrivateRoute.tsx diff --git a/package.json b/package.json index f49df1a..296c5a7 100644 --- a/package.json +++ b/package.json @@ -74,7 +74,9 @@ "grpc", "bg", "str", - "signup" + "signup", + "unary", + "invoker" ], "skipIfMatch": [ "TODO\\(.+\\):", diff --git a/src/App.tsx b/src/App.tsx index b167f0c..14d137f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,20 +1,34 @@ import React from 'react'; import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; -import { Projects, CreateProject, ProjectAPIKeys, Project, Documents, ProjectSettings, Login, Signup } from 'routes'; +import { + Login, + Signup, + PrivateRoute, + Projects, + CreateProject, + ProjectAPIKeys, + Project, + Documents, + ProjectSettings, +} from 'routes'; function App() { + // TODO(hackerwins): If the user is already logged in, redirect to the + // projects page. return ( } /> } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> + }> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + ); diff --git a/src/api/admin.proto b/src/api/admin.proto index 8c24549..4a98275 100644 --- a/src/api/admin.proto +++ b/src/api/admin.proto @@ -21,6 +21,9 @@ import "resources.proto"; // Admin is a service that provides a API for Admin. service Admin { + rpc SignUp(SignUpRequest) returns (SignUpResponse) {} + rpc LogIn(LogInRequest) returns (LogInResponse) {} + rpc CreateProject(CreateProjectRequest) returns (CreateProjectResponse) {} rpc ListProjects(ListProjectsRequest) returns (ListProjectsResponse) {} rpc GetProject(GetProjectRequest) returns (GetProjectResponse) {} @@ -34,6 +37,24 @@ service Admin { rpc ListChanges (ListChangesRequest) returns (ListChangesResponse) {} } +message SignUpRequest { + string username = 1; + string password = 2; +} + +message SignUpResponse { + User user = 1; +} + +message LogInRequest { + string username = 1; + string password = 2; +} + +message LogInResponse { + string token = 1; +} + message CreateProjectRequest { string name = 1; } diff --git a/src/api/admin_grpc_web_pb.d.ts b/src/api/admin_grpc_web_pb.d.ts index f46aefb..a26ae56 100644 --- a/src/api/admin_grpc_web_pb.d.ts +++ b/src/api/admin_grpc_web_pb.d.ts @@ -8,6 +8,20 @@ export class AdminClient { credentials?: null | { [index: string]: string; }, options?: null | { [index: string]: any; }); + signUp( + request: admin_pb.SignUpRequest, + metadata: grpcWeb.Metadata | undefined, + callback: (err: grpcWeb.RpcError, + response: admin_pb.SignUpResponse) => void + ): grpcWeb.ClientReadableStream; + + logIn( + request: admin_pb.LogInRequest, + metadata: grpcWeb.Metadata | undefined, + callback: (err: grpcWeb.RpcError, + response: admin_pb.LogInResponse) => void + ): grpcWeb.ClientReadableStream; + createProject( request: admin_pb.CreateProjectRequest, metadata: grpcWeb.Metadata | undefined, @@ -78,6 +92,16 @@ export class AdminPromiseClient { credentials?: null | { [index: string]: string; }, options?: null | { [index: string]: any; }); + signUp( + request: admin_pb.SignUpRequest, + metadata?: grpcWeb.Metadata + ): Promise; + + logIn( + request: admin_pb.LogInRequest, + metadata?: grpcWeb.Metadata + ): Promise; + createProject( request: admin_pb.CreateProjectRequest, metadata?: grpcWeb.Metadata diff --git a/src/api/admin_grpc_web_pb.js b/src/api/admin_grpc_web_pb.js index 26168b1..373a09a 100644 --- a/src/api/admin_grpc_web_pb.js +++ b/src/api/admin_grpc_web_pb.js @@ -72,6 +72,128 @@ proto.api.AdminPromiseClient = }; +/** + * @const + * @type {!grpc.web.MethodDescriptor< + * !proto.api.SignUpRequest, + * !proto.api.SignUpResponse>} + */ +const methodDescriptor_Admin_SignUp = new grpc.web.MethodDescriptor( + '/api.Admin/SignUp', + grpc.web.MethodType.UNARY, + proto.api.SignUpRequest, + proto.api.SignUpResponse, + /** + * @param {!proto.api.SignUpRequest} request + * @return {!Uint8Array} + */ + function(request) { + return request.serializeBinary(); + }, + proto.api.SignUpResponse.deserializeBinary +); + + +/** + * @param {!proto.api.SignUpRequest} request The + * request proto + * @param {?Object} metadata User defined + * call metadata + * @param {function(?grpc.web.RpcError, ?proto.api.SignUpResponse)} + * callback The callback function(error, response) + * @return {!grpc.web.ClientReadableStream|undefined} + * The XHR Node Readable Stream + */ +proto.api.AdminClient.prototype.signUp = + function(request, metadata, callback) { + return this.client_.rpcCall(this.hostname_ + + '/api.Admin/SignUp', + request, + metadata || {}, + methodDescriptor_Admin_SignUp, + callback); +}; + + +/** + * @param {!proto.api.SignUpRequest} request The + * request proto + * @param {?Object=} metadata User defined + * call metadata + * @return {!Promise} + * Promise that resolves to the response + */ +proto.api.AdminPromiseClient.prototype.signUp = + function(request, metadata) { + return this.client_.unaryCall(this.hostname_ + + '/api.Admin/SignUp', + request, + metadata || {}, + methodDescriptor_Admin_SignUp); +}; + + +/** + * @const + * @type {!grpc.web.MethodDescriptor< + * !proto.api.LogInRequest, + * !proto.api.LogInResponse>} + */ +const methodDescriptor_Admin_LogIn = new grpc.web.MethodDescriptor( + '/api.Admin/LogIn', + grpc.web.MethodType.UNARY, + proto.api.LogInRequest, + proto.api.LogInResponse, + /** + * @param {!proto.api.LogInRequest} request + * @return {!Uint8Array} + */ + function(request) { + return request.serializeBinary(); + }, + proto.api.LogInResponse.deserializeBinary +); + + +/** + * @param {!proto.api.LogInRequest} request The + * request proto + * @param {?Object} metadata User defined + * call metadata + * @param {function(?grpc.web.RpcError, ?proto.api.LogInResponse)} + * callback The callback function(error, response) + * @return {!grpc.web.ClientReadableStream|undefined} + * The XHR Node Readable Stream + */ +proto.api.AdminClient.prototype.logIn = + function(request, metadata, callback) { + return this.client_.rpcCall(this.hostname_ + + '/api.Admin/LogIn', + request, + metadata || {}, + methodDescriptor_Admin_LogIn, + callback); +}; + + +/** + * @param {!proto.api.LogInRequest} request The + * request proto + * @param {?Object=} metadata User defined + * call metadata + * @return {!Promise} + * Promise that resolves to the response + */ +proto.api.AdminPromiseClient.prototype.logIn = + function(request, metadata) { + return this.client_.unaryCall(this.hostname_ + + '/api.Admin/LogIn', + request, + metadata || {}, + methodDescriptor_Admin_LogIn); +}; + + /** * @const * @type {!grpc.web.MethodDescriptor< diff --git a/src/api/admin_pb.d.ts b/src/api/admin_pb.d.ts index 7a823a1..9fbcba6 100644 --- a/src/api/admin_pb.d.ts +++ b/src/api/admin_pb.d.ts @@ -3,6 +3,88 @@ import * as jspb from 'google-protobuf' import * as resources_pb from './resources_pb'; +export class SignUpRequest extends jspb.Message { + getUsername(): string; + setUsername(value: string): SignUpRequest; + + getPassword(): string; + setPassword(value: string): SignUpRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SignUpRequest.AsObject; + static toObject(includeInstance: boolean, msg: SignUpRequest): SignUpRequest.AsObject; + static serializeBinaryToWriter(message: SignUpRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SignUpRequest; + static deserializeBinaryFromReader(message: SignUpRequest, reader: jspb.BinaryReader): SignUpRequest; +} + +export namespace SignUpRequest { + export type AsObject = { + username: string, + password: string, + } +} + +export class SignUpResponse extends jspb.Message { + getUser(): resources_pb.User | undefined; + setUser(value?: resources_pb.User): SignUpResponse; + hasUser(): boolean; + clearUser(): SignUpResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): SignUpResponse.AsObject; + static toObject(includeInstance: boolean, msg: SignUpResponse): SignUpResponse.AsObject; + static serializeBinaryToWriter(message: SignUpResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): SignUpResponse; + static deserializeBinaryFromReader(message: SignUpResponse, reader: jspb.BinaryReader): SignUpResponse; +} + +export namespace SignUpResponse { + export type AsObject = { + user?: resources_pb.User.AsObject, + } +} + +export class LogInRequest extends jspb.Message { + getUsername(): string; + setUsername(value: string): LogInRequest; + + getPassword(): string; + setPassword(value: string): LogInRequest; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): LogInRequest.AsObject; + static toObject(includeInstance: boolean, msg: LogInRequest): LogInRequest.AsObject; + static serializeBinaryToWriter(message: LogInRequest, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): LogInRequest; + static deserializeBinaryFromReader(message: LogInRequest, reader: jspb.BinaryReader): LogInRequest; +} + +export namespace LogInRequest { + export type AsObject = { + username: string, + password: string, + } +} + +export class LogInResponse extends jspb.Message { + getToken(): string; + setToken(value: string): LogInResponse; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): LogInResponse.AsObject; + static toObject(includeInstance: boolean, msg: LogInResponse): LogInResponse.AsObject; + static serializeBinaryToWriter(message: LogInResponse, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): LogInResponse; + static deserializeBinaryFromReader(message: LogInResponse, reader: jspb.BinaryReader): LogInResponse; +} + +export namespace LogInResponse { + export type AsObject = { + token: string, + } +} + export class CreateProjectRequest extends jspb.Message { getName(): string; setName(value: string): CreateProjectRequest; diff --git a/src/api/admin_pb.js b/src/api/admin_pb.js index 65a7cfc..fe9180e 100644 --- a/src/api/admin_pb.js +++ b/src/api/admin_pb.js @@ -37,10 +37,98 @@ goog.exportSymbol('proto.api.ListDocumentsRequest', null, global); goog.exportSymbol('proto.api.ListDocumentsResponse', null, global); goog.exportSymbol('proto.api.ListProjectsRequest', null, global); goog.exportSymbol('proto.api.ListProjectsResponse', null, global); +goog.exportSymbol('proto.api.LogInRequest', null, global); +goog.exportSymbol('proto.api.LogInResponse', null, global); goog.exportSymbol('proto.api.SearchDocumentsRequest', null, global); goog.exportSymbol('proto.api.SearchDocumentsResponse', null, global); +goog.exportSymbol('proto.api.SignUpRequest', null, global); +goog.exportSymbol('proto.api.SignUpResponse', null, global); goog.exportSymbol('proto.api.UpdateProjectRequest', null, global); goog.exportSymbol('proto.api.UpdateProjectResponse', null, global); +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.api.SignUpRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.api.SignUpRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.api.SignUpRequest.displayName = 'proto.api.SignUpRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.api.SignUpResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.api.SignUpResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.api.SignUpResponse.displayName = 'proto.api.SignUpResponse'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.api.LogInRequest = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.api.LogInRequest, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.api.LogInRequest.displayName = 'proto.api.LogInRequest'; +} +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.api.LogInResponse = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.api.LogInResponse, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.api.LogInResponse.displayName = 'proto.api.LogInResponse'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -422,6 +510,607 @@ if (goog.DEBUG && !COMPILED) { +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.api.SignUpRequest.prototype.toObject = function(opt_includeInstance) { + return proto.api.SignUpRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.api.SignUpRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.SignUpRequest.toObject = function(includeInstance, msg) { + var f, obj = { + username: jspb.Message.getFieldWithDefault(msg, 1, ""), + password: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.api.SignUpRequest} + */ +proto.api.SignUpRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.api.SignUpRequest; + return proto.api.SignUpRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.api.SignUpRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.api.SignUpRequest} + */ +proto.api.SignUpRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setUsername(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setPassword(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.api.SignUpRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.api.SignUpRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.api.SignUpRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.SignUpRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUsername(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getPassword(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string username = 1; + * @return {string} + */ +proto.api.SignUpRequest.prototype.getUsername = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.SignUpRequest} returns this + */ +proto.api.SignUpRequest.prototype.setUsername = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string password = 2; + * @return {string} + */ +proto.api.SignUpRequest.prototype.getPassword = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.SignUpRequest} returns this + */ +proto.api.SignUpRequest.prototype.setPassword = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.api.SignUpResponse.prototype.toObject = function(opt_includeInstance) { + return proto.api.SignUpResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.api.SignUpResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.SignUpResponse.toObject = function(includeInstance, msg) { + var f, obj = { + user: (f = msg.getUser()) && resources_pb.User.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.api.SignUpResponse} + */ +proto.api.SignUpResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.api.SignUpResponse; + return proto.api.SignUpResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.api.SignUpResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.api.SignUpResponse} + */ +proto.api.SignUpResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = new resources_pb.User; + reader.readMessage(value,resources_pb.User.deserializeBinaryFromReader); + msg.setUser(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.api.SignUpResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.api.SignUpResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.api.SignUpResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.SignUpResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUser(); + if (f != null) { + writer.writeMessage( + 1, + f, + resources_pb.User.serializeBinaryToWriter + ); + } +}; + + +/** + * optional User user = 1; + * @return {?proto.api.User} + */ +proto.api.SignUpResponse.prototype.getUser = function() { + return /** @type{?proto.api.User} */ ( + jspb.Message.getWrapperField(this, resources_pb.User, 1)); +}; + + +/** + * @param {?proto.api.User|undefined} value + * @return {!proto.api.SignUpResponse} returns this +*/ +proto.api.SignUpResponse.prototype.setUser = function(value) { + return jspb.Message.setWrapperField(this, 1, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.api.SignUpResponse} returns this + */ +proto.api.SignUpResponse.prototype.clearUser = function() { + return this.setUser(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.api.SignUpResponse.prototype.hasUser = function() { + return jspb.Message.getField(this, 1) != null; +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.api.LogInRequest.prototype.toObject = function(opt_includeInstance) { + return proto.api.LogInRequest.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.api.LogInRequest} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.LogInRequest.toObject = function(includeInstance, msg) { + var f, obj = { + username: jspb.Message.getFieldWithDefault(msg, 1, ""), + password: jspb.Message.getFieldWithDefault(msg, 2, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.api.LogInRequest} + */ +proto.api.LogInRequest.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.api.LogInRequest; + return proto.api.LogInRequest.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.api.LogInRequest} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.api.LogInRequest} + */ +proto.api.LogInRequest.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setUsername(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setPassword(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.api.LogInRequest.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.api.LogInRequest.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.api.LogInRequest} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.LogInRequest.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getUsername(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getPassword(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } +}; + + +/** + * optional string username = 1; + * @return {string} + */ +proto.api.LogInRequest.prototype.getUsername = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.LogInRequest} returns this + */ +proto.api.LogInRequest.prototype.setUsername = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string password = 2; + * @return {string} + */ +proto.api.LogInRequest.prototype.getPassword = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.LogInRequest} returns this + */ +proto.api.LogInRequest.prototype.setPassword = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + + + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.api.LogInResponse.prototype.toObject = function(opt_includeInstance) { + return proto.api.LogInResponse.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.api.LogInResponse} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.LogInResponse.toObject = function(includeInstance, msg) { + var f, obj = { + token: jspb.Message.getFieldWithDefault(msg, 1, "") + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.api.LogInResponse} + */ +proto.api.LogInResponse.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.api.LogInResponse; + return proto.api.LogInResponse.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.api.LogInResponse} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.api.LogInResponse} + */ +proto.api.LogInResponse.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setToken(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.api.LogInResponse.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.api.LogInResponse.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.api.LogInResponse} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.LogInResponse.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getToken(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } +}; + + +/** + * optional string token = 1; + * @return {string} + */ +proto.api.LogInResponse.prototype.getToken = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.LogInResponse} returns this + */ +proto.api.LogInResponse.prototype.setToken = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + + + + if (jspb.Message.GENERATE_TO_OBJECT) { /** * Creates an object representation of this proto. diff --git a/src/api/auth_interceptor.ts b/src/api/auth_interceptor.ts new file mode 100644 index 0000000..c8f30b8 --- /dev/null +++ b/src/api/auth_interceptor.ts @@ -0,0 +1,79 @@ +/* + * Copyright 2022 The Yorkie Authors. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * `AuthUnaryInterceptor` is a unary interceptor to add the Authorization header for each + * request. + */ +export class AuthUnaryInterceptor { + private token?: string; + + constructor(token?: string) { + this.token = token; + } + + /** + * `setToken` sets the token to the interceptor. + * @param token The token to set. + */ + public setToken(token: string) { + this.token = token; + } + + /** + * `intercept` intercepts the request and adds the token to the metadata. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + public intercept(request: any, invoker: any): any { + const metadata = request.getMetadata(); + if (this.token) { + metadata['authorization'] = this.token; + } + return invoker(request); + } +} + +/** + * `AuthStreamInterceptor` is a stream interceptor to add the Authorization header for each + * request. + */ +export class AuthStreamInterceptor { + private token?: string; + + constructor(token?: string) { + this.token = token; + } + + /** + * `setToken` sets the token to the interceptor. + * @param token The token to set. + */ + public setToken(token: string) { + this.token = token; + } + + /** + * `intercept` intercepts the request and adds the token to the metadata. + */ + // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types + public intercept(request: any, invoker: any): any { + const metadata = request.getMetadata(); + if (this.token) { + metadata['authorization'] = this.token; + } + return invoker(request); + } +} diff --git a/src/api/converter.ts b/src/api/converter.ts index 1252387..97e679a 100644 --- a/src/api/converter.ts +++ b/src/api/converter.ts @@ -1,7 +1,18 @@ import { Timestamp as PbTimestamp } from 'google-protobuf/google/protobuf/timestamp_pb'; -import { Project, DocumentSummary, AuthWebhookMethod, ErrorWithDetails } from './types'; -import { Project as PbProject, DocumentSummary as PbDocumentSummary } from './resources_pb'; +import { + User, + Project, + DocumentSummary, + AuthWebhookMethod, + ErrorWithDetails, +} from './types'; + +import { + User as PbUser, + Project as PbProject, + DocumentSummary as PbDocumentSummary, +} from './resources_pb'; import * as errorDetails from 'grpc-web-error-details'; @@ -9,6 +20,14 @@ export function fromTimestamp(pbTimestamp: PbTimestamp): number { return pbTimestamp.getSeconds() + pbTimestamp.getNanos() / 1e9; } +export function fromUser(pbUser: PbUser): User { + return { + id: pbUser.getId(), + username: pbUser.getUsername(), + createdAt: fromTimestamp(pbUser.getCreatedAt()!), + }; +} + export function fromProject(pbProject: PbProject): Project { return { id: pbProject.getId(), diff --git a/src/api/index.ts b/src/api/index.ts index 94b5909..607cec4 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -1,5 +1,7 @@ import { AdminPromiseClient } from './admin_grpc_web_pb'; import { + LogInRequest, + SignUpRequest, ListProjectsRequest, ListDocumentsRequest, GetProjectRequest, @@ -10,6 +12,7 @@ import { } from './admin_pb'; import * as errorDetails from 'grpc-web-error-details'; +import { AuthUnaryInterceptor, AuthStreamInterceptor } from './auth_interceptor'; import { UpdatableProjectFields as PbProjectFields } from './resources_pb'; import * as PbWrappers from 'google-protobuf/google/protobuf/wrappers_pb'; @@ -18,47 +21,58 @@ import * as converter from './converter'; export * from './types'; -const client = new AdminPromiseClient(`${process.env.REACT_APP_ADMIN_ADDR}`, null); - -// loginUser logins the user and returns a user. -export async function loginUser(email: string, password: string): Promise { - // TODO(hackerwins): Implement login and token saving logic. - return Promise.resolve({ - email: 'dl_alto_dev@navercorp.com', - createdAt: 0, - }); +// TODO(hackerwins): Consider combining these two interceptors into one. +const unaryInterceptor = new AuthUnaryInterceptor(); +const streamInterceptor = new AuthStreamInterceptor(); +const client = new AdminPromiseClient(`${process.env.REACT_APP_ADMIN_ADDR}`, null, { + unaryInterceptors: [unaryInterceptor], + streamInterceptors: [streamInterceptor], +}); + +// logIn logs in the user and returns a token. +export async function logIn(username: string, password: string): Promise { + const req = new LogInRequest(); + req.setUsername(username); + req.setPassword(password); + const res = await client.logIn(req); + + // TODO(hackerwins): We should probably store the token in localStorage or cookies. + const token = res.getToken(); + unaryInterceptor.setToken(token); + streamInterceptor.setToken(token); + return token; } -// signupUser creates a new user and returns a user. -export async function signupUser(email: string, password: string): Promise { - // TODO(hackerwins): Implement signup and token saving logic. - return Promise.resolve({ - email: 'dl_alto_dev@navercorp.com', - createdAt: 0, - }); +// signUp signs up the user and returns a user. +export async function signUp(username: string, password: string): Promise { + const req = new SignUpRequest(); + req.setUsername(username); + req.setPassword(password); + const res = await client.signUp(req); + return converter.fromUser(res.getUser()!); } // createProject creates a new project. export async function createProject(name: string): Promise { const req = new CreateProjectRequest(); req.setName(name); - const response = await client.createProject(req); - return converter.fromProject(response.getProject()!); + const res = await client.createProject(req); + return converter.fromProject(res.getProject()!); } // listProjects fetches projects from the admin server. export async function listProjects(): Promise> { const req = new ListProjectsRequest(); - const response = await client.listProjects(req); - return converter.fromProjects(response.getProjectsList()); + const res = await client.listProjects(req); + return converter.fromProjects(res.getProjectsList()); } // getProject fetch project from the admin server. export async function getProject(name: string): Promise { const req = new GetProjectRequest(); req.setName(name); - const response = await client.getProject(req); - return converter.fromProject(response.getProject()!); + const res = await client.getProject(req); + return converter.fromProject(res.getProject()!); } // UpdateProject updates a project info. @@ -81,8 +95,8 @@ export async function updateProject(id: string, fields: UpdatableProjectFields): req.setFields(pbFields); try { - const response = await client.updateProject(req); - return converter.fromProject(response.getProject()!); + const res = await client.updateProject(req); + return converter.fromProject(res.getProject()!); } catch (error) { const [status, details] = errorDetails.statusFromError(error); if (!status || !details) { @@ -105,8 +119,8 @@ export async function listDocuments( req.setPreviousId(previousID); req.setPageSize(pageSize); req.setIsForward(isForward); - const response = await client.listDocuments(req); - const summaries = converter.fromDocumentSummaries(response.getDocumentsList()); + const res = await client.listDocuments(req); + const summaries = converter.fromDocumentSummaries(res.getDocumentsList()); if (isForward) { summaries.reverse(); } @@ -118,9 +132,9 @@ export async function getDocument(projectName: string, documentKey: string): Pro const req = new GetDocumentRequest(); req.setProjectName(projectName); req.setDocumentKey(documentKey); - const response = await client.getDocument(req); + const res = await client.getDocument(req); - const document = response.getDocument(); + const document = res.getDocument(); return converter.fromDocumentSummary(document!); } @@ -137,10 +151,10 @@ export async function searchDocuments( req.setProjectName(projectName); req.setQuery(documentQuery); req.setPageSize(pageSize); - const response = await client.searchDocuments(req); - const summaries = converter.fromDocumentSummaries(response.getDocumentsList()); + const res = await client.searchDocuments(req); + const summaries = converter.fromDocumentSummaries(res.getDocumentsList()); return { - totalCount: response.getTotalCount(), + totalCount: res.getTotalCount(), documents: summaries, }; } diff --git a/src/api/resources.proto b/src/api/resources.proto index 2c3f78a..62f6a3a 100644 --- a/src/api/resources.proto +++ b/src/api/resources.proto @@ -13,302 +13,307 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - syntax = "proto3"; - - import "google/protobuf/timestamp.proto"; - import "google/protobuf/wrappers.proto"; - - package api; - - ///////////////////////////////////////// - // Messages for ChangePack // - ///////////////////////////////////////// - - message ChangePack { - string document_key = 1; - Checkpoint checkpoint = 2; - bytes snapshot = 3; - repeated Change changes = 4; - TimeTicket min_synced_ticket = 5; - } - - message Change { - ChangeID id = 1; - string message = 2; - repeated Operation operations = 3; - } - - message ChangeID { - uint32 client_seq = 1; - uint64 server_seq = 2 [jstype = JS_STRING]; - uint64 lamport = 3 [jstype = JS_STRING]; - bytes actor_id = 4; - } - - message Operation { - message Set { - TimeTicket parent_created_at = 1; - string key = 2; - JSONElementSimple value = 3; - TimeTicket executed_at = 4; - } - message Add { - TimeTicket parent_created_at = 1; - TimeTicket prev_created_at = 2; - JSONElementSimple value = 3; - TimeTicket executed_at = 4; - } - message Move { - TimeTicket parent_created_at = 1; - TimeTicket prev_created_at = 2; - TimeTicket created_at = 3; - TimeTicket executed_at = 4; - } - message Remove { - TimeTicket parent_created_at = 1; - TimeTicket created_at = 2; - TimeTicket executed_at = 3; - } - message Edit { - TimeTicket parent_created_at = 1; - TextNodePos from = 2; - TextNodePos to = 3; - map created_at_map_by_actor = 4; - string content = 5; - TimeTicket executed_at = 6; - } - message Select { - TimeTicket parent_created_at = 1; - TextNodePos from = 2; - TextNodePos to = 3; - TimeTicket executed_at = 4; - } - message RichEdit { - TimeTicket parent_created_at = 1; - TextNodePos from = 2; - TextNodePos to = 3; - map created_at_map_by_actor = 4; - string content = 5; - map attributes = 6; - TimeTicket executed_at = 7; - } - message Style { - TimeTicket parent_created_at = 1; - TextNodePos from = 2; - TextNodePos to = 3; - map attributes = 4; - TimeTicket executed_at = 5; - } - message Increase { - TimeTicket parent_created_at = 1; - JSONElementSimple value = 2; - TimeTicket executed_at = 3; - } - - oneof body { - Set set = 1; - Add add = 2; - Move move = 3; - Remove remove = 4; - Edit edit = 5; - Select select = 6; - RichEdit rich_edit = 7; - Style style = 8; - Increase increase = 9; - } - } - - message JSONElementSimple { - TimeTicket created_at = 1; - TimeTicket moved_at = 2; - TimeTicket removed_at = 3; - ValueType type = 4; - bytes value = 5; - } - - ///////////////////////////////////////// - // Messages for JSON // - ///////////////////////////////////////// - - message JSONElement { - message JSONObject { - repeated RHTNode nodes = 1; - TimeTicket created_at = 2; - TimeTicket moved_at = 3; - TimeTicket removed_at = 4; - } - message JSONArray { - repeated RGANode nodes = 1; - TimeTicket created_at = 2; - TimeTicket moved_at = 3; - TimeTicket removed_at = 4; - } - message Primitive { - ValueType type = 1; - bytes value = 2; - TimeTicket created_at = 3; - TimeTicket moved_at = 4; - TimeTicket removed_at = 5; - } - message Text { - repeated TextNode nodes = 1; - TimeTicket created_at = 2; - TimeTicket moved_at = 3; - TimeTicket removed_at = 4; - } - message RichText { - repeated RichTextNode nodes = 1; - TimeTicket created_at = 2; - TimeTicket moved_at = 3; - TimeTicket removed_at = 4; - } - message Counter { - ValueType type = 1; - bytes value = 2; - TimeTicket created_at = 3; - TimeTicket moved_at = 4; - TimeTicket removed_at = 5; - } - - oneof Body { - JSONObject json_object = 1; - JSONArray json_array = 2; - Primitive primitive = 3; - Text text = 4; - RichText rich_text = 5; - Counter counter = 6; - } - } - - message RHTNode { - string key = 1; - JSONElement element = 2; - } - - message RGANode { - RGANode next = 1; - JSONElement element = 2; - } - - message TextNode { - TextNodeID id = 1; - string value = 2; - TimeTicket removed_at = 3; - TextNodeID ins_prev_id = 4; - } - - message RichTextNodeAttr { - string key = 1; - string value = 2; - TimeTicket updated_at = 3; - } - - message RichTextNode { - TextNodeID id = 1; - map attributes = 2; - string value = 3; - TimeTicket removed_at = 4; - TextNodeID ins_prev_id = 5; - } - - message TextNodeID { - TimeTicket created_at = 1; - int32 offset = 2; - } - - ///////////////////////////////////////// - // Messages for Common // - ///////////////////////////////////////// - - message Project { - string id = 1; - string name = 2; - string public_key = 3; - string secret_key = 4; - string auth_webhook_url = 5; - repeated string auth_webhook_methods = 6; - google.protobuf.Timestamp created_at = 7; - google.protobuf.Timestamp updated_at = 8; - } - - message UpdatableProjectFields { - message AuthWebhookMethods { - repeated string methods = 1; - } - - google.protobuf.StringValue name = 1; - google.protobuf.StringValue auth_webhook_url = 2; - AuthWebhookMethods auth_webhook_methods = 3; - } - - message DocumentSummary { - string id = 1; - string key = 2; - string snapshot = 3; - google.protobuf.Timestamp created_at = 4; - google.protobuf.Timestamp accessed_at = 5; - google.protobuf.Timestamp updated_at = 6; - } - - message Presence { - int32 clock = 1; - map data = 2; - } - - message Client { - bytes id = 1; - Presence presence = 2; - } - - message Clients { - repeated Client clients = 1; - } - - message Checkpoint { - uint64 server_seq = 1 [jstype = JS_STRING]; - uint32 client_seq = 2; - } - - message TextNodePos { - TimeTicket created_at = 1; - int32 offset = 2; - int32 relative_offset = 3; - } - - message TimeTicket { - uint64 lamport = 1 [jstype = JS_STRING]; - uint32 delimiter = 2; - bytes actor_id = 3; - } - - enum ValueType { - NULL = 0; - BOOLEAN = 1; - INTEGER = 2; - LONG = 3; - DOUBLE = 4; - STRING = 5; - BYTES = 6; - DATE = 7; - JSON_OBJECT = 8; - JSON_ARRAY = 9; - TEXT = 10; - RICH_TEXT = 11; - INTEGER_CNT = 12; - LONG_CNT = 13; - DOUBLE_CNT = 14; - } - - enum DocEventType { - DOCUMENTS_CHANGED = 0; - DOCUMENTS_WATCHED = 1; - DOCUMENTS_UNWATCHED = 2; - PRESENCE_CHANGED = 3; - } - - message DocEvent { - DocEventType type = 1; - Client publisher = 2; - repeated string document_keys = 3; - } - \ No newline at end of file +syntax = "proto3"; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +package api; + +///////////////////////////////////////// +// Messages for ChangePack // +///////////////////////////////////////// + +message ChangePack { + string document_key = 1; + Checkpoint checkpoint = 2; + bytes snapshot = 3; + repeated Change changes = 4; + TimeTicket min_synced_ticket = 5; +} + +message Change { + ChangeID id = 1; + string message = 2; + repeated Operation operations = 3; +} + +message ChangeID { + uint32 client_seq = 1; + uint64 server_seq = 2 [jstype = JS_STRING]; + uint64 lamport = 3 [jstype = JS_STRING]; + bytes actor_id = 4; +} + +message Operation { + message Set { + TimeTicket parent_created_at = 1; + string key = 2; + JSONElementSimple value = 3; + TimeTicket executed_at = 4; + } + message Add { + TimeTicket parent_created_at = 1; + TimeTicket prev_created_at = 2; + JSONElementSimple value = 3; + TimeTicket executed_at = 4; + } + message Move { + TimeTicket parent_created_at = 1; + TimeTicket prev_created_at = 2; + TimeTicket created_at = 3; + TimeTicket executed_at = 4; + } + message Remove { + TimeTicket parent_created_at = 1; + TimeTicket created_at = 2; + TimeTicket executed_at = 3; + } + message Edit { + TimeTicket parent_created_at = 1; + TextNodePos from = 2; + TextNodePos to = 3; + map created_at_map_by_actor = 4; + string content = 5; + TimeTicket executed_at = 6; + } + message Select { + TimeTicket parent_created_at = 1; + TextNodePos from = 2; + TextNodePos to = 3; + TimeTicket executed_at = 4; + } + message RichEdit { + TimeTicket parent_created_at = 1; + TextNodePos from = 2; + TextNodePos to = 3; + map created_at_map_by_actor = 4; + string content = 5; + map attributes = 6; + TimeTicket executed_at = 7; + } + message Style { + TimeTicket parent_created_at = 1; + TextNodePos from = 2; + TextNodePos to = 3; + map attributes = 4; + TimeTicket executed_at = 5; + } + message Increase { + TimeTicket parent_created_at = 1; + JSONElementSimple value = 2; + TimeTicket executed_at = 3; + } + + oneof body { + Set set = 1; + Add add = 2; + Move move = 3; + Remove remove = 4; + Edit edit = 5; + Select select = 6; + RichEdit rich_edit = 7; + Style style = 8; + Increase increase = 9; + } +} + +message JSONElementSimple { + TimeTicket created_at = 1; + TimeTicket moved_at = 2; + TimeTicket removed_at = 3; + ValueType type = 4; + bytes value = 5; +} + +///////////////////////////////////////// +// Messages for JSON // +///////////////////////////////////////// + +message JSONElement { + message JSONObject { + repeated RHTNode nodes = 1; + TimeTicket created_at = 2; + TimeTicket moved_at = 3; + TimeTicket removed_at = 4; + } + message JSONArray { + repeated RGANode nodes = 1; + TimeTicket created_at = 2; + TimeTicket moved_at = 3; + TimeTicket removed_at = 4; + } + message Primitive { + ValueType type = 1; + bytes value = 2; + TimeTicket created_at = 3; + TimeTicket moved_at = 4; + TimeTicket removed_at = 5; + } + message Text { + repeated TextNode nodes = 1; + TimeTicket created_at = 2; + TimeTicket moved_at = 3; + TimeTicket removed_at = 4; + } + message RichText { + repeated RichTextNode nodes = 1; + TimeTicket created_at = 2; + TimeTicket moved_at = 3; + TimeTicket removed_at = 4; + } + message Counter { + ValueType type = 1; + bytes value = 2; + TimeTicket created_at = 3; + TimeTicket moved_at = 4; + TimeTicket removed_at = 5; + } + + oneof Body { + JSONObject json_object = 1; + JSONArray json_array = 2; + Primitive primitive = 3; + Text text = 4; + RichText rich_text = 5; + Counter counter = 6; + } +} + +message RHTNode { + string key = 1; + JSONElement element = 2; +} + +message RGANode { + RGANode next = 1; + JSONElement element = 2; +} + +message TextNode { + TextNodeID id = 1; + string value = 2; + TimeTicket removed_at = 3; + TextNodeID ins_prev_id = 4; +} + +message RichTextNodeAttr { + string key = 1; + string value = 2; + TimeTicket updated_at = 3; +} + +message RichTextNode { + TextNodeID id = 1; + map attributes = 2; + string value = 3; + TimeTicket removed_at = 4; + TextNodeID ins_prev_id = 5; +} + +message TextNodeID { + TimeTicket created_at = 1; + int32 offset = 2; +} + +///////////////////////////////////////// +// Messages for Common // +///////////////////////////////////////// + +message User { + string id = 1; + string username = 2; + google.protobuf.Timestamp created_at = 3; +} + +message Project { + string id = 1; + string name = 2; + string public_key = 3; + string secret_key = 4; + string auth_webhook_url = 5; + repeated string auth_webhook_methods = 6; + google.protobuf.Timestamp created_at = 7; + google.protobuf.Timestamp updated_at = 8; +} + +message UpdatableProjectFields { + message AuthWebhookMethods { + repeated string methods = 1; + } + + google.protobuf.StringValue name = 1; + google.protobuf.StringValue auth_webhook_url = 2; + AuthWebhookMethods auth_webhook_methods = 3; +} + +message DocumentSummary { + string id = 1; + string key = 2; + string snapshot = 3; + google.protobuf.Timestamp created_at = 4; + google.protobuf.Timestamp accessed_at = 5; + google.protobuf.Timestamp updated_at = 6; +} + +message Presence { + int32 clock = 1; + map data = 2; +} + +message Client { + bytes id = 1; + Presence presence = 2; +} + +message Clients { + repeated Client clients = 1; +} + +message Checkpoint { + uint64 server_seq = 1 [jstype = JS_STRING]; + uint32 client_seq = 2; +} + +message TextNodePos { + TimeTicket created_at = 1; + int32 offset = 2; + int32 relative_offset = 3; +} + +message TimeTicket { + uint64 lamport = 1 [jstype = JS_STRING]; + uint32 delimiter = 2; + bytes actor_id = 3; +} + +enum ValueType { + NULL = 0; + BOOLEAN = 1; + INTEGER = 2; + LONG = 3; + DOUBLE = 4; + STRING = 5; + BYTES = 6; + DATE = 7; + JSON_OBJECT = 8; + JSON_ARRAY = 9; + TEXT = 10; + RICH_TEXT = 11; + INTEGER_CNT = 12; + LONG_CNT = 13; + DOUBLE_CNT = 14; +} + +enum DocEventType { + DOCUMENTS_CHANGED = 0; + DOCUMENTS_WATCHED = 1; + DOCUMENTS_UNWATCHED = 2; + PRESENCE_CHANGED = 3; +} + +message DocEvent { + DocEventType type = 1; + Client publisher = 2; + repeated string document_keys = 3; +} diff --git a/src/api/resources_pb.d.ts b/src/api/resources_pb.d.ts index 527067d..52142d8 100644 --- a/src/api/resources_pb.d.ts +++ b/src/api/resources_pb.d.ts @@ -1076,6 +1076,34 @@ export namespace TextNodeID { } } +export class User extends jspb.Message { + getId(): string; + setId(value: string): User; + + getUsername(): string; + setUsername(value: string): User; + + getCreatedAt(): google_protobuf_timestamp_pb.Timestamp | undefined; + setCreatedAt(value?: google_protobuf_timestamp_pb.Timestamp): User; + hasCreatedAt(): boolean; + clearCreatedAt(): User; + + serializeBinary(): Uint8Array; + toObject(includeInstance?: boolean): User.AsObject; + static toObject(includeInstance: boolean, msg: User): User.AsObject; + static serializeBinaryToWriter(message: User, writer: jspb.BinaryWriter): void; + static deserializeBinary(bytes: Uint8Array): User; + static deserializeBinaryFromReader(message: User, reader: jspb.BinaryReader): User; +} + +export namespace User { + export type AsObject = { + id: string, + username: string, + createdAt?: google_protobuf_timestamp_pb.Timestamp.AsObject, + } +} + export class Project extends jspb.Message { getId(): string; setId(value: string): Project; diff --git a/src/api/resources_pb.js b/src/api/resources_pb.js index 4cf5de0..d68e565 100644 --- a/src/api/resources_pb.js +++ b/src/api/resources_pb.js @@ -66,6 +66,7 @@ goog.exportSymbol('proto.api.TextNodePos', null, global); goog.exportSymbol('proto.api.TimeTicket', null, global); goog.exportSymbol('proto.api.UpdatableProjectFields', null, global); goog.exportSymbol('proto.api.UpdatableProjectFields.AuthWebhookMethods', null, global); +goog.exportSymbol('proto.api.User', null, global); goog.exportSymbol('proto.api.ValueType', null, global); /** * Generated by JsPbCodeGenerator. @@ -634,6 +635,27 @@ if (goog.DEBUG && !COMPILED) { */ proto.api.TextNodeID.displayName = 'proto.api.TextNodeID'; } +/** + * Generated by JsPbCodeGenerator. + * @param {Array=} opt_data Optional initial data array, typically from a + * server response, or constructed directly in Javascript. The array is used + * in place and becomes part of the constructed object. It is not cloned. + * If no data is provided, the constructed object will be empty, but still + * valid. + * @extends {jspb.Message} + * @constructor + */ +proto.api.User = function(opt_data) { + jspb.Message.initialize(this, opt_data, 0, -1, null, null); +}; +goog.inherits(proto.api.User, jspb.Message); +if (goog.DEBUG && !COMPILED) { + /** + * @public + * @override + */ + proto.api.User.displayName = 'proto.api.User'; +} /** * Generated by JsPbCodeGenerator. * @param {Array=} opt_data Optional initial data array, typically from a @@ -9167,6 +9189,217 @@ proto.api.TextNodeID.prototype.setOffset = function(value) { + + +if (jspb.Message.GENERATE_TO_OBJECT) { +/** + * Creates an object representation of this proto. + * Field names that are reserved in JavaScript and will be renamed to pb_name. + * Optional fields that are not set will be set to undefined. + * To access a reserved field use, foo.pb_, eg, foo.pb_default. + * For the list of reserved names please see: + * net/proto2/compiler/js/internal/generator.cc#kKeyword. + * @param {boolean=} opt_includeInstance Deprecated. whether to include the + * JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @return {!Object} + */ +proto.api.User.prototype.toObject = function(opt_includeInstance) { + return proto.api.User.toObject(opt_includeInstance, this); +}; + + +/** + * Static version of the {@see toObject} method. + * @param {boolean|undefined} includeInstance Deprecated. Whether to include + * the JSPB instance for transitional soy proto support: + * http://goto/soy-param-migration + * @param {!proto.api.User} msg The msg instance to transform. + * @return {!Object} + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.User.toObject = function(includeInstance, msg) { + var f, obj = { + id: jspb.Message.getFieldWithDefault(msg, 1, ""), + username: jspb.Message.getFieldWithDefault(msg, 2, ""), + createdAt: (f = msg.getCreatedAt()) && google_protobuf_timestamp_pb.Timestamp.toObject(includeInstance, f) + }; + + if (includeInstance) { + obj.$jspbMessageInstance = msg; + } + return obj; +}; +} + + +/** + * Deserializes binary data (in protobuf wire format). + * @param {jspb.ByteSource} bytes The bytes to deserialize. + * @return {!proto.api.User} + */ +proto.api.User.deserializeBinary = function(bytes) { + var reader = new jspb.BinaryReader(bytes); + var msg = new proto.api.User; + return proto.api.User.deserializeBinaryFromReader(msg, reader); +}; + + +/** + * Deserializes binary data (in protobuf wire format) from the + * given reader into the given message object. + * @param {!proto.api.User} msg The message object to deserialize into. + * @param {!jspb.BinaryReader} reader The BinaryReader to use. + * @return {!proto.api.User} + */ +proto.api.User.deserializeBinaryFromReader = function(msg, reader) { + while (reader.nextField()) { + if (reader.isEndGroup()) { + break; + } + var field = reader.getFieldNumber(); + switch (field) { + case 1: + var value = /** @type {string} */ (reader.readString()); + msg.setId(value); + break; + case 2: + var value = /** @type {string} */ (reader.readString()); + msg.setUsername(value); + break; + case 3: + var value = new google_protobuf_timestamp_pb.Timestamp; + reader.readMessage(value,google_protobuf_timestamp_pb.Timestamp.deserializeBinaryFromReader); + msg.setCreatedAt(value); + break; + default: + reader.skipField(); + break; + } + } + return msg; +}; + + +/** + * Serializes the message to binary data (in protobuf wire format). + * @return {!Uint8Array} + */ +proto.api.User.prototype.serializeBinary = function() { + var writer = new jspb.BinaryWriter(); + proto.api.User.serializeBinaryToWriter(this, writer); + return writer.getResultBuffer(); +}; + + +/** + * Serializes the given message to binary data (in protobuf wire + * format), writing to the given BinaryWriter. + * @param {!proto.api.User} message + * @param {!jspb.BinaryWriter} writer + * @suppress {unusedLocalVariables} f is only used for nested messages + */ +proto.api.User.serializeBinaryToWriter = function(message, writer) { + var f = undefined; + f = message.getId(); + if (f.length > 0) { + writer.writeString( + 1, + f + ); + } + f = message.getUsername(); + if (f.length > 0) { + writer.writeString( + 2, + f + ); + } + f = message.getCreatedAt(); + if (f != null) { + writer.writeMessage( + 3, + f, + google_protobuf_timestamp_pb.Timestamp.serializeBinaryToWriter + ); + } +}; + + +/** + * optional string id = 1; + * @return {string} + */ +proto.api.User.prototype.getId = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 1, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.User} returns this + */ +proto.api.User.prototype.setId = function(value) { + return jspb.Message.setProto3StringField(this, 1, value); +}; + + +/** + * optional string username = 2; + * @return {string} + */ +proto.api.User.prototype.getUsername = function() { + return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, "")); +}; + + +/** + * @param {string} value + * @return {!proto.api.User} returns this + */ +proto.api.User.prototype.setUsername = function(value) { + return jspb.Message.setProto3StringField(this, 2, value); +}; + + +/** + * optional google.protobuf.Timestamp created_at = 3; + * @return {?proto.google.protobuf.Timestamp} + */ +proto.api.User.prototype.getCreatedAt = function() { + return /** @type{?proto.google.protobuf.Timestamp} */ ( + jspb.Message.getWrapperField(this, google_protobuf_timestamp_pb.Timestamp, 3)); +}; + + +/** + * @param {?proto.google.protobuf.Timestamp|undefined} value + * @return {!proto.api.User} returns this +*/ +proto.api.User.prototype.setCreatedAt = function(value) { + return jspb.Message.setWrapperField(this, 3, value); +}; + + +/** + * Clears the message field making it undefined. + * @return {!proto.api.User} returns this + */ +proto.api.User.prototype.clearCreatedAt = function() { + return this.setCreatedAt(undefined); +}; + + +/** + * Returns whether this field is set. + * @return {boolean} + */ +proto.api.User.prototype.hasCreatedAt = function() { + return jspb.Message.getField(this, 3) != null; +}; + + + /** * List of repeated fields within this message type. * @private {!Array} diff --git a/src/api/types.ts b/src/api/types.ts index 70c7e95..6a9647b 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -8,7 +8,8 @@ export type DocumentSummary = { }; export type User = { - email: string; + id: string; + username: string; createdAt: number; }; diff --git a/src/features/users/LoginForm.tsx b/src/features/users/LoginForm.tsx index 72cc41e..65b6119 100644 --- a/src/features/users/LoginForm.tsx +++ b/src/features/users/LoginForm.tsx @@ -9,14 +9,13 @@ export function LoginForm() { const dispatch = useDispatch(); const navigate = useNavigate(); const { register, formState: { errors }, handleSubmit } = useForm(); - const { isSuccess } = useSelector(selectUsers); + const { login: { isSuccess } } = useSelector(selectUsers); const onSubmit = (data: LoginFields) => { dispatch(loginUser(data)); }; useEffect(() => { - // TODO(hackerwins): Go to dashboard if the user is already logged in. if (isSuccess) { navigate('/'); } @@ -29,10 +28,10 @@ export function LoginForm() { >

Log in to Yorkie

- - {errors.email &&

Email required.

} + + {errors.username &&

Username required.

}