From b9e72b1278ee8aa8958a757569d832c2fbfff535 Mon Sep 17 00:00:00 2001 From: Michael Geers Date: Wed, 29 Apr 2026 14:49:44 +0200 Subject: [PATCH] Sponsor: prepare for GitHub alternative (#29043) --- api/proto/auth.proto | 25 ++ api/proto/pb/auth.pb.go | 303 +++++++++++++++++-- api/proto/pb/auth_grpc.pb.go | 83 ++++- assets/js/components/Config/SponsorModal.vue | 225 +++++++++++--- assets/js/components/Savings/Sponsor.vue | 3 +- i18n/de.json | 11 +- i18n/en.json | 11 +- server/http_config_site_other_handler.go | 34 ++- tests/sponsor.spec.ts | 6 +- util/sponsor/auth.go | 64 +++- util/sponsor/hardware.go | 56 ++++ util/sponsor/hemspro.go | 17 ++ util/sponsor/hemspro_linux.go | 20 +- util/sponsor/victron.go | 41 +-- 14 files changed, 766 insertions(+), 133 deletions(-) create mode 100644 util/sponsor/hardware.go diff --git a/api/proto/auth.proto b/api/proto/auth.proto index a0917dd26..b1c10908c 100644 --- a/api/proto/auth.proto +++ b/api/proto/auth.proto @@ -8,6 +8,8 @@ option go_package = "proto/pb"; service Auth { rpc IsAuthorized (AuthRequest) returns (AuthReply) {} + rpc Activate (ActivateRequest) returns (ActivateReply) {} + rpc IsAuthorizedHardware (HardwareRequest) returns (HardwareReply) {} } message AuthRequest { @@ -18,4 +20,27 @@ message AuthReply { bool authorized = 1; string subject = 2; google.protobuf.Timestamp expires_at = 3; + string activation_key = 4; +} + +message ActivateRequest { + string key = 1; + string email = 2; + string machine_id = 3; +} + +message ActivateReply { + string token = 1; + string error = 2; +} + +message HardwareRequest { + string machine_id = 1; + string vendor = 2; + map metadata = 3; +} + +message HardwareReply { + bool authorized = 1; + string subject = 2; } diff --git a/api/proto/pb/auth.pb.go b/api/proto/pb/auth.pb.go index 92d3f5cb0..c67817048 100644 --- a/api/proto/pb/auth.pb.go +++ b/api/proto/pb/auth.pb.go @@ -1,18 +1,19 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: // protoc-gen-go v1.36.11 -// protoc v6.33.2 +// protoc v7.34.1 // source: proto/auth.proto package pb import ( - protoreflect "google.golang.org/protobuf/reflect/protoreflect" - protoimpl "google.golang.org/protobuf/runtime/protoimpl" - timestamppb "google.golang.org/protobuf/types/known/timestamppb" reflect "reflect" sync "sync" unsafe "unsafe" + + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" ) const ( @@ -71,6 +72,7 @@ type AuthReply struct { Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` ExpiresAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=expires_at,json=expiresAt,proto3" json:"expires_at,omitempty"` + ActivationKey string `protobuf:"bytes,4,opt,name=activation_key,json=activationKey,proto3" json:"activation_key,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -126,23 +128,278 @@ func (x *AuthReply) GetExpiresAt() *timestamppb.Timestamp { return nil } +func (x *AuthReply) GetActivationKey() string { + if x != nil { + return x.ActivationKey + } + return "" +} + +type ActivateRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + Email string `protobuf:"bytes,2,opt,name=email,proto3" json:"email,omitempty"` + MachineId string `protobuf:"bytes,3,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ActivateRequest) Reset() { + *x = ActivateRequest{} + mi := &file_proto_auth_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ActivateRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivateRequest) ProtoMessage() {} + +func (x *ActivateRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_auth_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivateRequest.ProtoReflect.Descriptor instead. +func (*ActivateRequest) Descriptor() ([]byte, []int) { + return file_proto_auth_proto_rawDescGZIP(), []int{2} +} + +func (x *ActivateRequest) GetKey() string { + if x != nil { + return x.Key + } + return "" +} + +func (x *ActivateRequest) GetEmail() string { + if x != nil { + return x.Email + } + return "" +} + +func (x *ActivateRequest) GetMachineId() string { + if x != nil { + return x.MachineId + } + return "" +} + +type ActivateReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Token string `protobuf:"bytes,1,opt,name=token,proto3" json:"token,omitempty"` + Error string `protobuf:"bytes,2,opt,name=error,proto3" json:"error,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ActivateReply) Reset() { + *x = ActivateReply{} + mi := &file_proto_auth_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ActivateReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ActivateReply) ProtoMessage() {} + +func (x *ActivateReply) ProtoReflect() protoreflect.Message { + mi := &file_proto_auth_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ActivateReply.ProtoReflect.Descriptor instead. +func (*ActivateReply) Descriptor() ([]byte, []int) { + return file_proto_auth_proto_rawDescGZIP(), []int{3} +} + +func (x *ActivateReply) GetToken() string { + if x != nil { + return x.Token + } + return "" +} + +func (x *ActivateReply) GetError() string { + if x != nil { + return x.Error + } + return "" +} + +type HardwareRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + MachineId string `protobuf:"bytes,1,opt,name=machine_id,json=machineId,proto3" json:"machine_id,omitempty"` + Vendor string `protobuf:"bytes,2,opt,name=vendor,proto3" json:"vendor,omitempty"` + Metadata map[string]string `protobuf:"bytes,3,rep,name=metadata,proto3" json:"metadata,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HardwareRequest) Reset() { + *x = HardwareRequest{} + mi := &file_proto_auth_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HardwareRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HardwareRequest) ProtoMessage() {} + +func (x *HardwareRequest) ProtoReflect() protoreflect.Message { + mi := &file_proto_auth_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HardwareRequest.ProtoReflect.Descriptor instead. +func (*HardwareRequest) Descriptor() ([]byte, []int) { + return file_proto_auth_proto_rawDescGZIP(), []int{4} +} + +func (x *HardwareRequest) GetMachineId() string { + if x != nil { + return x.MachineId + } + return "" +} + +func (x *HardwareRequest) GetVendor() string { + if x != nil { + return x.Vendor + } + return "" +} + +func (x *HardwareRequest) GetMetadata() map[string]string { + if x != nil { + return x.Metadata + } + return nil +} + +type HardwareReply struct { + state protoimpl.MessageState `protogen:"open.v1"` + Authorized bool `protobuf:"varint,1,opt,name=authorized,proto3" json:"authorized,omitempty"` + Subject string `protobuf:"bytes,2,opt,name=subject,proto3" json:"subject,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *HardwareReply) Reset() { + *x = HardwareReply{} + mi := &file_proto_auth_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *HardwareReply) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*HardwareReply) ProtoMessage() {} + +func (x *HardwareReply) ProtoReflect() protoreflect.Message { + mi := &file_proto_auth_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use HardwareReply.ProtoReflect.Descriptor instead. +func (*HardwareReply) Descriptor() ([]byte, []int) { + return file_proto_auth_proto_rawDescGZIP(), []int{5} +} + +func (x *HardwareReply) GetAuthorized() bool { + if x != nil { + return x.Authorized + } + return false +} + +func (x *HardwareReply) GetSubject() string { + if x != nil { + return x.Subject + } + return "" +} + var File_proto_auth_proto protoreflect.FileDescriptor const file_proto_auth_proto_rawDesc = "" + "\n" + "\x10proto/auth.proto\x1a\x1fgoogle/protobuf/timestamp.proto\"#\n" + "\vAuthRequest\x12\x14\n" + - "\x05token\x18\x01 \x01(\tR\x05token\"\x80\x01\n" + + "\x05token\x18\x01 \x01(\tR\x05token\"\xa7\x01\n" + "\tAuthReply\x12\x1e\n" + "\n" + "authorized\x18\x01 \x01(\bR\n" + "authorized\x12\x18\n" + "\asubject\x18\x02 \x01(\tR\asubject\x129\n" + "\n" + - "expires_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt22\n" + + "expires_at\x18\x03 \x01(\v2\x1a.google.protobuf.TimestampR\texpiresAt\x12%\n" + + "\x0eactivation_key\x18\x04 \x01(\tR\ractivationKey\"X\n" + + "\x0fActivateRequest\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05email\x18\x02 \x01(\tR\x05email\x12\x1d\n" + + "\n" + + "machine_id\x18\x03 \x01(\tR\tmachineId\";\n" + + "\rActivateReply\x12\x14\n" + + "\x05token\x18\x01 \x01(\tR\x05token\x12\x14\n" + + "\x05error\x18\x02 \x01(\tR\x05error\"\xc1\x01\n" + + "\x0fHardwareRequest\x12\x1d\n" + + "\n" + + "machine_id\x18\x01 \x01(\tR\tmachineId\x12\x16\n" + + "\x06vendor\x18\x02 \x01(\tR\x06vendor\x12:\n" + + "\bmetadata\x18\x03 \x03(\v2\x1e.HardwareRequest.MetadataEntryR\bmetadata\x1a;\n" + + "\rMetadataEntry\x12\x10\n" + + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"I\n" + + "\rHardwareReply\x12\x1e\n" + + "\n" + + "authorized\x18\x01 \x01(\bR\n" + + "authorized\x12\x18\n" + + "\asubject\x18\x02 \x01(\tR\asubject2\x9e\x01\n" + "\x04Auth\x12*\n" + "\fIsAuthorized\x12\f.AuthRequest\x1a\n" + - ".AuthReply\"\x00B\n" + + ".AuthReply\"\x00\x12.\n" + + "\bActivate\x12\x10.ActivateRequest\x1a\x0e.ActivateReply\"\x00\x12:\n" + + "\x14IsAuthorizedHardware\x12\x10.HardwareRequest\x1a\x0e.HardwareReply\"\x00B\n" + "Z\bproto/pbb\x06proto3" var ( @@ -157,21 +414,31 @@ func file_proto_auth_proto_rawDescGZIP() []byte { return file_proto_auth_proto_rawDescData } -var file_proto_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_proto_auth_proto_msgTypes = make([]protoimpl.MessageInfo, 7) var file_proto_auth_proto_goTypes = []any{ (*AuthRequest)(nil), // 0: AuthRequest (*AuthReply)(nil), // 1: AuthReply - (*timestamppb.Timestamp)(nil), // 2: google.protobuf.Timestamp + (*ActivateRequest)(nil), // 2: ActivateRequest + (*ActivateReply)(nil), // 3: ActivateReply + (*HardwareRequest)(nil), // 4: HardwareRequest + (*HardwareReply)(nil), // 5: HardwareReply + nil, // 6: HardwareRequest.MetadataEntry + (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp } var file_proto_auth_proto_depIdxs = []int32{ - 2, // 0: AuthReply.expires_at:type_name -> google.protobuf.Timestamp - 0, // 1: Auth.IsAuthorized:input_type -> AuthRequest - 1, // 2: Auth.IsAuthorized:output_type -> AuthReply - 2, // [2:3] is the sub-list for method output_type - 1, // [1:2] is the sub-list for method input_type - 1, // [1:1] is the sub-list for extension type_name - 1, // [1:1] is the sub-list for extension extendee - 0, // [0:1] is the sub-list for field type_name + 7, // 0: AuthReply.expires_at:type_name -> google.protobuf.Timestamp + 6, // 1: HardwareRequest.metadata:type_name -> HardwareRequest.MetadataEntry + 0, // 2: Auth.IsAuthorized:input_type -> AuthRequest + 2, // 3: Auth.Activate:input_type -> ActivateRequest + 4, // 4: Auth.IsAuthorizedHardware:input_type -> HardwareRequest + 1, // 5: Auth.IsAuthorized:output_type -> AuthReply + 3, // 6: Auth.Activate:output_type -> ActivateReply + 5, // 7: Auth.IsAuthorizedHardware:output_type -> HardwareReply + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name } func init() { file_proto_auth_proto_init() } @@ -185,7 +452,7 @@ func file_proto_auth_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: unsafe.Slice(unsafe.StringData(file_proto_auth_proto_rawDesc), len(file_proto_auth_proto_rawDesc)), NumEnums: 0, - NumMessages: 2, + NumMessages: 7, NumExtensions: 0, NumServices: 1, }, diff --git a/api/proto/pb/auth_grpc.pb.go b/api/proto/pb/auth_grpc.pb.go index 6896a3e50..9155efaac 100644 --- a/api/proto/pb/auth_grpc.pb.go +++ b/api/proto/pb/auth_grpc.pb.go @@ -1,13 +1,14 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. // versions: -// - protoc-gen-go-grpc v1.6.0 -// - protoc v6.33.2 +// - protoc-gen-go-grpc v1.6.1 +// - protoc v7.34.1 // source: proto/auth.proto package pb import ( context "context" + grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" @@ -19,7 +20,9 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( - Auth_IsAuthorized_FullMethodName = "/Auth/IsAuthorized" + Auth_IsAuthorized_FullMethodName = "/Auth/IsAuthorized" + Auth_Activate_FullMethodName = "/Auth/Activate" + Auth_IsAuthorizedHardware_FullMethodName = "/Auth/IsAuthorizedHardware" ) // AuthClient is the client API for Auth service. @@ -27,6 +30,8 @@ const ( // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type AuthClient interface { IsAuthorized(ctx context.Context, in *AuthRequest, opts ...grpc.CallOption) (*AuthReply, error) + Activate(ctx context.Context, in *ActivateRequest, opts ...grpc.CallOption) (*ActivateReply, error) + IsAuthorizedHardware(ctx context.Context, in *HardwareRequest, opts ...grpc.CallOption) (*HardwareReply, error) } type authClient struct { @@ -47,11 +52,33 @@ func (c *authClient) IsAuthorized(ctx context.Context, in *AuthRequest, opts ... return out, nil } +func (c *authClient) Activate(ctx context.Context, in *ActivateRequest, opts ...grpc.CallOption) (*ActivateReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(ActivateReply) + err := c.cc.Invoke(ctx, Auth_Activate_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *authClient) IsAuthorizedHardware(ctx context.Context, in *HardwareRequest, opts ...grpc.CallOption) (*HardwareReply, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(HardwareReply) + err := c.cc.Invoke(ctx, Auth_IsAuthorizedHardware_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + // AuthServer is the server API for Auth service. // All implementations must embed UnimplementedAuthServer // for forward compatibility. type AuthServer interface { IsAuthorized(context.Context, *AuthRequest) (*AuthReply, error) + Activate(context.Context, *ActivateRequest) (*ActivateReply, error) + IsAuthorizedHardware(context.Context, *HardwareRequest) (*HardwareReply, error) mustEmbedUnimplementedAuthServer() } @@ -65,6 +92,12 @@ type UnimplementedAuthServer struct{} func (UnimplementedAuthServer) IsAuthorized(context.Context, *AuthRequest) (*AuthReply, error) { return nil, status.Error(codes.Unimplemented, "method IsAuthorized not implemented") } +func (UnimplementedAuthServer) Activate(context.Context, *ActivateRequest) (*ActivateReply, error) { + return nil, status.Error(codes.Unimplemented, "method Activate not implemented") +} +func (UnimplementedAuthServer) IsAuthorizedHardware(context.Context, *HardwareRequest) (*HardwareReply, error) { + return nil, status.Error(codes.Unimplemented, "method IsAuthorizedHardware not implemented") +} func (UnimplementedAuthServer) mustEmbedUnimplementedAuthServer() {} func (UnimplementedAuthServer) testEmbeddedByValue() {} @@ -104,6 +137,42 @@ func _Auth_IsAuthorized_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } +func _Auth_Activate_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ActivateRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).Activate(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Auth_Activate_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).Activate(ctx, req.(*ActivateRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Auth_IsAuthorizedHardware_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(HardwareRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(AuthServer).IsAuthorizedHardware(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: Auth_IsAuthorizedHardware_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(AuthServer).IsAuthorizedHardware(ctx, req.(*HardwareRequest)) + } + return interceptor(ctx, in, info, handler) +} + // Auth_ServiceDesc is the grpc.ServiceDesc for Auth service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -115,6 +184,14 @@ var Auth_ServiceDesc = grpc.ServiceDesc{ MethodName: "IsAuthorized", Handler: _Auth_IsAuthorized_Handler, }, + { + MethodName: "Activate", + Handler: _Auth_Activate_Handler, + }, + { + MethodName: "IsAuthorizedHardware", + Handler: _Auth_IsAuthorizedHardware_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "proto/auth.proto", diff --git a/assets/js/components/Config/SponsorModal.vue b/assets/js/components/Config/SponsorModal.vue index 88d4ce9c0..28cdd0165 100644 --- a/assets/js/components/Config/SponsorModal.vue +++ b/assets/js/components/Config/SponsorModal.vue @@ -11,7 +11,10 @@ :disable-remove="!hasUiToken" disable-cancel @changed="$emit('changed')" - @open="showForm = false" + @open=" + showForm = false; + activeTab = 'github'; + " >