3k release: Connect RPC migration and authentication refactoring (#666)

This commit is contained in:
James Read 2025-10-24 23:42:39 +00:00 committed by GitHub
commit eb2463aa2d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
29 changed files with 634 additions and 6638 deletions

View File

@ -8,7 +8,8 @@ on:
tags: tags:
- '*' - '*'
branches: branches:
- '*' - main
- next
jobs: jobs:
build: build:
@ -55,6 +56,10 @@ jobs:
username: ${{ github.actor }} username: ${{ github.actor }}
password: ${{ secrets.CONTAINER_TOKEN }} password: ${{ secrets.CONTAINER_TOKEN }}
- name: get date
run: |
echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
- name: make webui - name: make webui
run: make -w webui-dist run: make -w webui-dist
@ -93,10 +98,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }} GITHUB_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }} GH_TOKEN: ${{ secrets.CONTAINER_TOKEN }}
- name: get date
run: |
echo "DATE=$(date +'%Y-%m-%d')" >> "$GITHUB_ENV"
- name: Archive binaries - name: Archive binaries
uses: actions/upload-artifact@v4.3.1 uses: actions/upload-artifact@v4.3.1
with: with:

View File

@ -35,6 +35,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- Footer visibility is controlled by `showFooter` from Init API; tests may assert the footer is absent when config disables it. - Footer visibility is controlled by `showFooter` from Init API; tests may assert the footer is absent when config disables it.
### Coding Standards (Go) ### Coding Standards (Go)
- Avoid adding superflous comments that explain what the code is doing. Comments are only to describe business logic decisions.
- Prefer clear, descriptive names; avoid 1–2 letter identifiers. - Prefer clear, descriptive names; avoid 1–2 letter identifiers.
- Use early returns and handle edge cases first. - Use early returns and handle edge cases first.
- Do not swallow errors; propagate or log meaningfully. - Do not swallow errors; propagate or log meaningfully.
@ -56,7 +57,7 @@ If you are looking for OliveTin's AI policy, you can find it in `AI.md`.
- Action button behavior: `frontend/resources/vue/ActionButton.vue` - Action button behavior: `frontend/resources/vue/ActionButton.vue`
### Contributing Checklist ### Contributing Checklist
- Review the contributuing guidelines at `CONTRIBUTING.adoc`. - Review the contributing guidelines at `CONTRIBUTING.adoc`.
- Review the AI guidance in `AI.md`. - Review the AI guidance in `AI.md`.
- Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`. - Review the pull request template at `.github/PULL_REQUEST_TEMPLATE.md`.

View File

@ -45,10 +45,10 @@ cd OliveTin
make githooks make githooks
# Step3: compile binary for current dev env (OS, ARCH) # Step3: compile binary for current dev env (OS, ARCH)
# `make grpc` will also run `make go-tools`, which installs "buf". This binary # `make proto` will also run `make go-tools`, which installs "buf". This binary
# will be put in your GOPATH/bin/, which should be on your path. buf is used to # will be put in your GOPATH/bin/, which should be on your path. buf is used to
# generate the protobuf / grpc stubs. # generate the protobuf / Connect RPC stubs.
make grpc make proto
make make
./OliveTin ./OliveTin
``` ```
@ -58,7 +58,7 @@ make
The project layout is reasonably straightforward; The project layout is reasonably straightforward;
* See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any). * See the `Makefile` for common targets. This project was originally created on top of Fedora, but it should be usable on Debian/your faveourite distro with minor changes (if any).
* The API is defined in protobuf+grpc - you will need to `make grpc`. * The API is defined in protobuf+Connect RPC - you will need to `make proto`.
* The Go daemon is built from the `cmd` and `internal` directories mostly. * The Go daemon is built from the `cmd` and `internal` directories mostly.
* The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver. * The webui is just a single page application with a bit of Javascript in the `webui` directory. This can happily be hosted on another webserver.

View File

@ -17,15 +17,12 @@ it:
go-tools: go-tools:
$(MAKE) -wC service go-tools $(MAKE) -wC service go-tools
proto: grpc proto: go-tools
grpc: go-tools
$(MAKE) -wC proto $(MAKE) -wC proto
dist: protoc dist:
echo "dist noop"
protoc:
protoc --go_out=. --go-grpc_out=. --grpc-gateway_out=. -I .:/usr/include/ OliveTin.proto
podman-image: podman-image:
buildah bud -t olivetin buildah bud -t olivetin
@ -59,4 +56,4 @@ clean:
$(call delete-files,reports) $(call delete-files,reports)
$(call delete-files,gen) $(call delete-files,gen)
.PHONY: grpc proto service .PHONY: proto service

View File

@ -47,7 +47,7 @@ All documentation can be found at [docs.olivetin.app](https://docs.olivetin.app)
* **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously. * **Accessible** - passes all the accessibility checks in Firefox, and issues with accessibility are taken seriously.
* **Container** - available for quickly testing and getting it up and running, great for the selfhosted community. * **Container** - available for quickly testing and getting it up and running, great for the selfhosted community.
* **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretically you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin. * **Integrate with anything** - OliveTin just runs Linux shell commands, so theoretically you could integrate with a bunch of stuff just by using curl, ping, etc. However, writing your own shell scripts is a great way to extend OliveTin.
* **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/gRPC API. * **Lightweight on resources** - uses only a few MB of RAM and barely any CPU. Written in Go, with a web interface written as a modern, responsive, Single Page App that uses the REST/Connect RPC API.
* **Good amount of unit tests and style checks** - helps potential contributors be consistent, and helps with maintainability. * **Good amount of unit tests and style checks** - helps potential contributors be consistent, and helps with maintainability.
## Screenshots ## Screenshots

View File

@ -1,273 +0,0 @@
class ArgumentForm extends window.HTMLElement {
getQueryParams () {
return new URLSearchParams(window.location.search.substring(1))
}
setup (json, callback) {
this.setAttribute('class', 'action-arguments')
this.constructTemplate()
this.domTitle.innerText = json.title
this.domIcon.innerHTML = json.icon
this.createDomFormArguments(json.arguments)
this.domBtnStart.onclick = () => {
for (const arg of this.argInputs) {
if (!arg.validity.valid) {
return
}
}
const argvs = this.getArgumentValues()
callback(argvs)
this.remove()
}
this.domBtnCancel.onclick = () => {
this.clearBookmark()
this.remove()
}
}
getArgumentValues () {
const ret = []
for (const arg of this.argInputs) {
if (arg.type === 'checkbox') {
if (arg.checked) {
arg.value = '1'
} else {
arg.value = '0'
}
}
if (arg.name === '') {
continue
}
ret.push({
name: arg.name,
value: arg.value
})
}
return ret
}
constructTemplate () {
const tpl = document.getElementById('tplArgumentForm')
const content = tpl.content.cloneNode(true)
this.appendChild(content)
this.domTitle = this.querySelector('h2')
this.domIcon = this.querySelector('span.icon')
this.domWrapper = this.querySelector('.wrapper')
this.domArgs = this.querySelector('.arguments')
this.domBtnStart = this.querySelector('[name=start]')
this.domBtnCancel = this.querySelector('[name=cancel]')
}
createDomFormArguments (args) {
this.argInputs = []
for (const arg of args) {
this.domArgs.appendChild(this.createDomLabel(arg))
this.domArgs.appendChild(this.createDomSuggestions(arg))
this.domArgs.appendChild(this.createDomInput(arg))
this.domArgs.appendChild(this.createDomDescription(arg))
}
}
createDomLabel (arg) {
const domLbl = document.createElement('label')
const lastChar = arg.title.charAt(arg.title.length - 1)
if (lastChar === '?' || lastChar === '.' || lastChar === ':') {
domLbl.innerHTML = arg.title
} else {
domLbl.innerHTML = arg.title + ':'
}
domLbl.setAttribute('for', arg.name)
return domLbl
}
createDomSuggestions (arg) {
if (typeof arg.suggestions !== 'object' || arg.suggestions.length === 0) {
return document.createElement('span')
}
const ret = document.createElement('datalist')
ret.setAttribute('id', arg.name + '-choices')
for (const suggestion of Object.keys(arg.suggestions)) {
const opt = document.createElement('option')
opt.setAttribute('value', suggestion)
if (typeof arg.suggestions[suggestion] !== 'undefined' && arg.suggestions[suggestion].length > 0) {
opt.innerText = arg.suggestions[suggestion]
}
ret.appendChild(opt)
}
return ret
}
createDomInput (arg) {
let domEl = null
if (arg.choices.length > 0 && (arg.type === 'select' || arg.type === '')) {
domEl = document.createElement('select')
// select/choice elements don't get an onchange/validation because theoretically
// the user should only select from a dropdown of valid options. The choices are
// riggeriously checked on StartAction anyway. ValidateArgumentType is only
// meant for showing simple warnings in the UI before running.
for (const choice of arg.choices) {
domEl.appendChild(this.createSelectOption(choice))
}
} else {
switch (arg.type) {
case 'html':
domEl = document.createElement('div')
domEl.innerHTML = arg.defaultValue
return domEl
case 'confirmation':
this.domBtnStart.disabled = true
domEl = document.createElement('input')
domEl.setAttribute('type', 'checkbox')
domEl.onchange = () => {
this.domBtnStart.disabled = false
domEl.disabled = true
}
break
case 'raw_string_multiline':
domEl = document.createElement('textarea')
domEl.setAttribute('rows', '5')
domEl.style.resize = 'vertical'
break
case 'datetime':
domEl = document.createElement('input')
domEl.setAttribute('type', 'datetime-local')
domEl.setAttribute('step', '1')
break
case 'checkbox':
domEl = document.createElement('input')
domEl.setAttribute('type', 'checkbox')
domEl.setAttribute('name', arg.name)
domEl.setAttribute('value', '1')
break
case 'password':
case 'email':
domEl = document.createElement('input')
domEl.setAttribute('type', arg.type)
break
default:
domEl = document.createElement('input')
if (arg.type.startsWith('regex:')) {
domEl.setAttribute('pattern', arg.type.replace('regex:', ''))
}
domEl.onchange = () => {
this.formatValidation(domEl, arg)
}
}
}
domEl.name = arg.name
// Use query parameter value if available
const params = this.getQueryParams()
const paramValue = params.get(arg.name)
if (paramValue !== null) {
domEl.value = paramValue
} else {
domEl.value = arg.defaultValue
}
// update the URL when a parameter is changed
domEl.addEventListener('change', this.updateUrlWithArg)
if (typeof arg.suggestions === 'object' && Object.keys(arg.suggestions).length > 0) {
domEl.setAttribute('list', arg.name + '-choices')
}
this.argInputs.push(domEl)
return domEl
}
async formatValidation (domEl, arg) {
const validateArgumentTypeArgs = {
value: domEl.value,
type: arg.type
}
const validation = await window.validateArgumentType(validateArgumentTypeArgs)
if (validation.valid) {
domEl.setCustomValidity('')
} else {
domEl.setCustomValidity(validation.description)
}
}
updateUrlWithArg (ev) {
if (!ev.target.name) {
return
}
const url = new URL(window.location.href)
if (ev.target.type === 'password') {
return
}
// copy the parameter value
url.searchParams.set(ev.target.name, ev.target.value)
// Update the URL without reloading the page
window.history.replaceState({}, '', url.toString())
}
createDomDescription (arg) {
const domArgumentDescription = document.createElement('span')
domArgumentDescription.classList.add('argument-description')
domArgumentDescription.innerHTML = arg.description
return domArgumentDescription
}
createSelectOption (choice) {
const domEl = document.createElement('option')
domEl.setAttribute('value', choice.value)
domEl.innerText = choice.title
return domEl
}
clearBookmark () {
// remove the action from the URL
window.history.replaceState({
path: window.location.pathname
}, '', window.location.pathname)
}
}
window.customElements.define('argument-form', ArgumentForm)

View File

@ -1,29 +0,0 @@
export class ExecutionFeedbackButton extends window.HTMLElement {
onExecutionFinished (LogEntry) {
if (LogEntry.timedOut) {
this.renderExecutionResult('action-timeout', 'Timed out')
} else if (LogEntry.blocked) {
this.renderExecutionResult('action-blocked', 'Blocked!')
} else if (LogEntry.exitCode !== 0) {
this.renderExecutionResult('action-nonzero-exit', 'Exit code ' + LogEntry.exitCode)
} else {
this.ellapsed = Math.ceil(new Date(LogEntry.datetimeFinished) - new Date(LogEntry.datetimeStarted)) / 1000
this.renderExecutionResult('action-success', 'Success!')
}
}
renderExecutionResult (resultCssClass, temporaryStatusMessage) {
this.updateDom(resultCssClass, '[' + temporaryStatusMessage + ']')
this.onExecStatusChanged()
}
updateDom (resultCssClass, title) {
if (resultCssClass == null) {
this.btn.className = ''
} else {
this.btn.classList.add(resultCssClass)
}
this.domTitle.innerText = title
}
}

View File

@ -4,7 +4,7 @@
<h2>Start action: {{ title }}</h2> <h2>Start action: {{ title }}</h2>
</div> </div>
<div class="section-content padding"> <div class="section-content padding">
<form @submit.prevent="handleSubmit"> <form @submit="handleSubmit">
<template v-if="actionArguments.length > 0"> <template v-if="actionArguments.length > 0">
<template v-for="arg in actionArguments" :key="arg.name" class="argument-group"> <template v-for="arg in actionArguments" :key="arg.name" class="argument-group">
@ -40,7 +40,7 @@
</div> </div>
<div class="buttons"> <div class="buttons">
<button name="start" type="submit" :disabled="!isFormValid || (hasConfirmation && !confirmationChecked)"> <button name="start" type="submit" :disabled="hasConfirmation && !confirmationChecked">
Start Start
</button> </button>
<button name="cancel" type="button" @click="handleCancel"> <button name="cancel" type="button" @click="handleCancel">
@ -53,11 +53,10 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted, nextTick } from 'vue' import { ref, onMounted, nextTick } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
const router = useRouter() const router = useRouter()
const emit = defineEmits(['submit', 'cancel', 'close'])
// Reactive data // Reactive data
const dialog = ref(null) const dialog = ref(null)
@ -71,7 +70,6 @@ const formErrors = ref({})
const actionArguments = ref([]) const actionArguments = ref([])
// Computed properties // Computed properties
const isFormValid = computed(() => Object.keys(formErrors.value).length === 0)
const props = defineProps({ const props = defineProps({
bindingId: { bindingId: {
@ -87,7 +85,6 @@ async function setup() {
}) })
const action = ret.action const action = ret.action
console.log('action', action)
title.value = action.title title.value = action.title
icon.value = action.icon icon.value = action.icon
@ -106,6 +103,14 @@ async function setup() {
hasConfirmation.value = true hasConfirmation.value = true
} }
}) })
// Run initial validation on all fields after DOM is updated
await nextTick()
for (const arg of actionArguments.value) {
if (arg.type && !arg.type.startsWith('regex:') && arg.type !== 'select' && arg.type !== '') {
await validateArgument(arg, argValues.value[arg.name])
}
}
} }
function getQueryParamValue(paramName) { function getQueryParamValue(paramName) {
@ -186,15 +191,31 @@ async function validateArgument(arg, value) {
type: arg.type type: arg.type
} }
const validation = await window.validateArgumentType(validateArgumentTypeArgs) const validation = await window.client.validateArgumentType(validateArgumentTypeArgs)
// Get the input element to set custom validity
const inputElement = document.getElementById(arg.name)
if (validation.valid) { if (validation.valid) {
delete formErrors.value[arg.name] delete formErrors.value[arg.name]
// Clear custom validity message
if (inputElement) {
inputElement.setCustomValidity('')
}
} else { } else {
formErrors.value[arg.name] = validation.description formErrors.value[arg.name] = validation.description
// Set custom validity message
if (inputElement) {
inputElement.setCustomValidity(validation.description)
}
} }
} catch (err) { } catch (err) {
console.warn('Validation failed:', err) console.warn('Validation failed:', err)
// On error, clear any custom validity
const inputElement = document.getElementById(arg.name)
if (inputElement) {
inputElement.setCustomValidity('')
}
} }
} }
@ -232,40 +253,66 @@ function getArgumentValues() {
return ret return ret
} }
function handleSubmit() { function getUniqueId() {
// Validate all inputs if (window.isSecureContext) {
let isValid = true return window.crypto.randomUUID()
} else {
return Date.now().toString()
}
}
async function startAction(actionArgs) {
const startActionArgs = {
bindingId: props.bindingId,
arguments: actionArgs,
uniqueTrackingId: getUniqueId()
}
try {
await window.client.startAction(startActionArgs)
console.log('Action started successfully with tracking ID:', startActionArgs.uniqueTrackingId)
} catch (err) {
console.error('Failed to start action:', err)
}
}
async function handleSubmit(event) {
// Set custom validity for required fields
for (const arg of actionArguments.value) { for (const arg of actionArguments.value) {
const value = argValues.value[arg.name] const value = argValues.value[arg.name]
const inputElement = document.getElementById(arg.name)
if (arg.required && (!value || value === '')) { if (arg.required && (!value || value === '')) {
formErrors.value[arg.name] = 'This field is required' formErrors.value[arg.name] = 'This field is required'
isValid = false // Set custom validity for required field validation
if (inputElement) {
inputElement.setCustomValidity('This field is required')
}
} }
} }
if (!isValid) { const form = event.target
if (!form.checkValidity()) {
console.log('argument form has elements that failed validation')
return return
} }
event.preventDefault()
const argvs = getArgumentValues() const argvs = getArgumentValues()
emit('submit', argvs) console.log('argument form has elements that passed validation')
close()
await startAction(argvs)
router.back()
} }
function handleCancel() { function handleCancel() {
router.back() router.back()
clearBookmark() clearBookmark()
emit('cancel')
close()
}
function handleClose() {
emit('close')
} }
function clearBookmark() { function clearBookmark() {
// Remove the action from the URL
window.history.replaceState({ window.history.replaceState({
path: window.location.pathname path: window.location.pathname
}, '', window.location.pathname) }, '', window.location.pathname)

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,700 +0,0 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.3.0
// - protoc (unknown)
// source: OliveTin.proto
package grpc
import (
context "context"
httpbody "google.golang.org/genproto/googleapis/api/httpbody"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
)
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later.
const _ = grpc.SupportPackageIsVersion7
const (
OliveTinApiService_GetDashboardComponents_FullMethodName = "/OliveTinApiService/GetDashboardComponents"
OliveTinApiService_StartAction_FullMethodName = "/OliveTinApiService/StartAction"
OliveTinApiService_StartActionAndWait_FullMethodName = "/OliveTinApiService/StartActionAndWait"
OliveTinApiService_StartActionByGet_FullMethodName = "/OliveTinApiService/StartActionByGet"
OliveTinApiService_StartActionByGetAndWait_FullMethodName = "/OliveTinApiService/StartActionByGetAndWait"
OliveTinApiService_KillAction_FullMethodName = "/OliveTinApiService/KillAction"
OliveTinApiService_ExecutionStatus_FullMethodName = "/OliveTinApiService/ExecutionStatus"
OliveTinApiService_GetLogs_FullMethodName = "/OliveTinApiService/GetLogs"
OliveTinApiService_ValidateArgumentType_FullMethodName = "/OliveTinApiService/ValidateArgumentType"
OliveTinApiService_WhoAmI_FullMethodName = "/OliveTinApiService/WhoAmI"
OliveTinApiService_SosReport_FullMethodName = "/OliveTinApiService/SosReport"
OliveTinApiService_DumpVars_FullMethodName = "/OliveTinApiService/DumpVars"
OliveTinApiService_DumpPublicIdActionMap_FullMethodName = "/OliveTinApiService/DumpPublicIdActionMap"
OliveTinApiService_GetReadyz_FullMethodName = "/OliveTinApiService/GetReadyz"
OliveTinApiService_LocalUserLogin_FullMethodName = "/OliveTinApiService/LocalUserLogin"
OliveTinApiService_PasswordHash_FullMethodName = "/OliveTinApiService/PasswordHash"
OliveTinApiService_Logout_FullMethodName = "/OliveTinApiService/Logout"
)
// OliveTinApiServiceClient is the client API for OliveTinApiService service.
//
// 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 OliveTinApiServiceClient interface {
GetDashboardComponents(ctx context.Context, in *GetDashboardComponentsRequest, opts ...grpc.CallOption) (*GetDashboardComponentsResponse, error)
StartAction(ctx context.Context, in *StartActionRequest, opts ...grpc.CallOption) (*StartActionResponse, error)
StartActionAndWait(ctx context.Context, in *StartActionAndWaitRequest, opts ...grpc.CallOption) (*StartActionAndWaitResponse, error)
StartActionByGet(ctx context.Context, in *StartActionByGetRequest, opts ...grpc.CallOption) (*StartActionByGetResponse, error)
StartActionByGetAndWait(ctx context.Context, in *StartActionByGetAndWaitRequest, opts ...grpc.CallOption) (*StartActionByGetAndWaitResponse, error)
KillAction(ctx context.Context, in *KillActionRequest, opts ...grpc.CallOption) (*KillActionResponse, error)
ExecutionStatus(ctx context.Context, in *ExecutionStatusRequest, opts ...grpc.CallOption) (*ExecutionStatusResponse, error)
GetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*GetLogsResponse, error)
ValidateArgumentType(ctx context.Context, in *ValidateArgumentTypeRequest, opts ...grpc.CallOption) (*ValidateArgumentTypeResponse, error)
WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error)
SosReport(ctx context.Context, in *SosReportRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
DumpVars(ctx context.Context, in *DumpVarsRequest, opts ...grpc.CallOption) (*DumpVarsResponse, error)
DumpPublicIdActionMap(ctx context.Context, in *DumpPublicIdActionMapRequest, opts ...grpc.CallOption) (*DumpPublicIdActionMapResponse, error)
GetReadyz(ctx context.Context, in *GetReadyzRequest, opts ...grpc.CallOption) (*GetReadyzResponse, error)
LocalUserLogin(ctx context.Context, in *LocalUserLoginRequest, opts ...grpc.CallOption) (*LocalUserLoginResponse, error)
PasswordHash(ctx context.Context, in *PasswordHashRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error)
}
type oliveTinApiServiceClient struct {
cc grpc.ClientConnInterface
}
func NewOliveTinApiServiceClient(cc grpc.ClientConnInterface) OliveTinApiServiceClient {
return &oliveTinApiServiceClient{cc}
}
func (c *oliveTinApiServiceClient) GetDashboardComponents(ctx context.Context, in *GetDashboardComponentsRequest, opts ...grpc.CallOption) (*GetDashboardComponentsResponse, error) {
out := new(GetDashboardComponentsResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_GetDashboardComponents_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) StartAction(ctx context.Context, in *StartActionRequest, opts ...grpc.CallOption) (*StartActionResponse, error) {
out := new(StartActionResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_StartAction_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) StartActionAndWait(ctx context.Context, in *StartActionAndWaitRequest, opts ...grpc.CallOption) (*StartActionAndWaitResponse, error) {
out := new(StartActionAndWaitResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionAndWait_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) StartActionByGet(ctx context.Context, in *StartActionByGetRequest, opts ...grpc.CallOption) (*StartActionByGetResponse, error) {
out := new(StartActionByGetResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionByGet_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) StartActionByGetAndWait(ctx context.Context, in *StartActionByGetAndWaitRequest, opts ...grpc.CallOption) (*StartActionByGetAndWaitResponse, error) {
out := new(StartActionByGetAndWaitResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_StartActionByGetAndWait_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) KillAction(ctx context.Context, in *KillActionRequest, opts ...grpc.CallOption) (*KillActionResponse, error) {
out := new(KillActionResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_KillAction_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) ExecutionStatus(ctx context.Context, in *ExecutionStatusRequest, opts ...grpc.CallOption) (*ExecutionStatusResponse, error) {
out := new(ExecutionStatusResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_ExecutionStatus_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) GetLogs(ctx context.Context, in *GetLogsRequest, opts ...grpc.CallOption) (*GetLogsResponse, error) {
out := new(GetLogsResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_GetLogs_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) ValidateArgumentType(ctx context.Context, in *ValidateArgumentTypeRequest, opts ...grpc.CallOption) (*ValidateArgumentTypeResponse, error) {
out := new(ValidateArgumentTypeResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_ValidateArgumentType_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) WhoAmI(ctx context.Context, in *WhoAmIRequest, opts ...grpc.CallOption) (*WhoAmIResponse, error) {
out := new(WhoAmIResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_WhoAmI_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) SosReport(ctx context.Context, in *SosReportRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
out := new(httpbody.HttpBody)
err := c.cc.Invoke(ctx, OliveTinApiService_SosReport_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) DumpVars(ctx context.Context, in *DumpVarsRequest, opts ...grpc.CallOption) (*DumpVarsResponse, error) {
out := new(DumpVarsResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_DumpVars_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) DumpPublicIdActionMap(ctx context.Context, in *DumpPublicIdActionMapRequest, opts ...grpc.CallOption) (*DumpPublicIdActionMapResponse, error) {
out := new(DumpPublicIdActionMapResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_DumpPublicIdActionMap_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) GetReadyz(ctx context.Context, in *GetReadyzRequest, opts ...grpc.CallOption) (*GetReadyzResponse, error) {
out := new(GetReadyzResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_GetReadyz_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) LocalUserLogin(ctx context.Context, in *LocalUserLoginRequest, opts ...grpc.CallOption) (*LocalUserLoginResponse, error) {
out := new(LocalUserLoginResponse)
err := c.cc.Invoke(ctx, OliveTinApiService_LocalUserLogin_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) PasswordHash(ctx context.Context, in *PasswordHashRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
out := new(httpbody.HttpBody)
err := c.cc.Invoke(ctx, OliveTinApiService_PasswordHash_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *oliveTinApiServiceClient) Logout(ctx context.Context, in *LogoutRequest, opts ...grpc.CallOption) (*httpbody.HttpBody, error) {
out := new(httpbody.HttpBody)
err := c.cc.Invoke(ctx, OliveTinApiService_Logout_FullMethodName, in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// OliveTinApiServiceServer is the server API for OliveTinApiService service.
// All implementations should embed UnimplementedOliveTinApiServiceServer
// for forward compatibility
type OliveTinApiServiceServer interface {
GetDashboardComponents(context.Context, *GetDashboardComponentsRequest) (*GetDashboardComponentsResponse, error)
StartAction(context.Context, *StartActionRequest) (*StartActionResponse, error)
StartActionAndWait(context.Context, *StartActionAndWaitRequest) (*StartActionAndWaitResponse, error)
StartActionByGet(context.Context, *StartActionByGetRequest) (*StartActionByGetResponse, error)
StartActionByGetAndWait(context.Context, *StartActionByGetAndWaitRequest) (*StartActionByGetAndWaitResponse, error)
KillAction(context.Context, *KillActionRequest) (*KillActionResponse, error)
ExecutionStatus(context.Context, *ExecutionStatusRequest) (*ExecutionStatusResponse, error)
GetLogs(context.Context, *GetLogsRequest) (*GetLogsResponse, error)
ValidateArgumentType(context.Context, *ValidateArgumentTypeRequest) (*ValidateArgumentTypeResponse, error)
WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error)
SosReport(context.Context, *SosReportRequest) (*httpbody.HttpBody, error)
DumpVars(context.Context, *DumpVarsRequest) (*DumpVarsResponse, error)
DumpPublicIdActionMap(context.Context, *DumpPublicIdActionMapRequest) (*DumpPublicIdActionMapResponse, error)
GetReadyz(context.Context, *GetReadyzRequest) (*GetReadyzResponse, error)
LocalUserLogin(context.Context, *LocalUserLoginRequest) (*LocalUserLoginResponse, error)
PasswordHash(context.Context, *PasswordHashRequest) (*httpbody.HttpBody, error)
Logout(context.Context, *LogoutRequest) (*httpbody.HttpBody, error)
}
// UnimplementedOliveTinApiServiceServer should be embedded to have forward compatible implementations.
type UnimplementedOliveTinApiServiceServer struct {
}
func (UnimplementedOliveTinApiServiceServer) GetDashboardComponents(context.Context, *GetDashboardComponentsRequest) (*GetDashboardComponentsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetDashboardComponents not implemented")
}
func (UnimplementedOliveTinApiServiceServer) StartAction(context.Context, *StartActionRequest) (*StartActionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartAction not implemented")
}
func (UnimplementedOliveTinApiServiceServer) StartActionAndWait(context.Context, *StartActionAndWaitRequest) (*StartActionAndWaitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartActionAndWait not implemented")
}
func (UnimplementedOliveTinApiServiceServer) StartActionByGet(context.Context, *StartActionByGetRequest) (*StartActionByGetResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartActionByGet not implemented")
}
func (UnimplementedOliveTinApiServiceServer) StartActionByGetAndWait(context.Context, *StartActionByGetAndWaitRequest) (*StartActionByGetAndWaitResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method StartActionByGetAndWait not implemented")
}
func (UnimplementedOliveTinApiServiceServer) KillAction(context.Context, *KillActionRequest) (*KillActionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method KillAction not implemented")
}
func (UnimplementedOliveTinApiServiceServer) ExecutionStatus(context.Context, *ExecutionStatusRequest) (*ExecutionStatusResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ExecutionStatus not implemented")
}
func (UnimplementedOliveTinApiServiceServer) GetLogs(context.Context, *GetLogsRequest) (*GetLogsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetLogs not implemented")
}
func (UnimplementedOliveTinApiServiceServer) ValidateArgumentType(context.Context, *ValidateArgumentTypeRequest) (*ValidateArgumentTypeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method ValidateArgumentType not implemented")
}
func (UnimplementedOliveTinApiServiceServer) WhoAmI(context.Context, *WhoAmIRequest) (*WhoAmIResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method WhoAmI not implemented")
}
func (UnimplementedOliveTinApiServiceServer) SosReport(context.Context, *SosReportRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method SosReport not implemented")
}
func (UnimplementedOliveTinApiServiceServer) DumpVars(context.Context, *DumpVarsRequest) (*DumpVarsResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DumpVars not implemented")
}
func (UnimplementedOliveTinApiServiceServer) DumpPublicIdActionMap(context.Context, *DumpPublicIdActionMapRequest) (*DumpPublicIdActionMapResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method DumpPublicIdActionMap not implemented")
}
func (UnimplementedOliveTinApiServiceServer) GetReadyz(context.Context, *GetReadyzRequest) (*GetReadyzResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetReadyz not implemented")
}
func (UnimplementedOliveTinApiServiceServer) LocalUserLogin(context.Context, *LocalUserLoginRequest) (*LocalUserLoginResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method LocalUserLogin not implemented")
}
func (UnimplementedOliveTinApiServiceServer) PasswordHash(context.Context, *PasswordHashRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method PasswordHash not implemented")
}
func (UnimplementedOliveTinApiServiceServer) Logout(context.Context, *LogoutRequest) (*httpbody.HttpBody, error) {
return nil, status.Errorf(codes.Unimplemented, "method Logout not implemented")
}
// UnsafeOliveTinApiServiceServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to OliveTinApiServiceServer will
// result in compilation errors.
type UnsafeOliveTinApiServiceServer interface {
mustEmbedUnimplementedOliveTinApiServiceServer()
}
func RegisterOliveTinApiServiceServer(s grpc.ServiceRegistrar, srv OliveTinApiServiceServer) {
s.RegisterService(&OliveTinApiService_ServiceDesc, srv)
}
func _OliveTinApiService_GetDashboardComponents_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetDashboardComponentsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).GetDashboardComponents(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_GetDashboardComponents_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).GetDashboardComponents(ctx, req.(*GetDashboardComponentsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_StartAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartActionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).StartAction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_StartAction_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).StartAction(ctx, req.(*StartActionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_StartActionAndWait_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartActionAndWaitRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).StartActionAndWait(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_StartActionAndWait_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).StartActionAndWait(ctx, req.(*StartActionAndWaitRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_StartActionByGet_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartActionByGetRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).StartActionByGet(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_StartActionByGet_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).StartActionByGet(ctx, req.(*StartActionByGetRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_StartActionByGetAndWait_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(StartActionByGetAndWaitRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).StartActionByGetAndWait(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_StartActionByGetAndWait_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).StartActionByGetAndWait(ctx, req.(*StartActionByGetAndWaitRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_KillAction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(KillActionRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).KillAction(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_KillAction_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).KillAction(ctx, req.(*KillActionRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_ExecutionStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ExecutionStatusRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).ExecutionStatus(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_ExecutionStatus_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).ExecutionStatus(ctx, req.(*ExecutionStatusRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_GetLogs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetLogsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).GetLogs(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_GetLogs_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).GetLogs(ctx, req.(*GetLogsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_ValidateArgumentType_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ValidateArgumentTypeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).ValidateArgumentType(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_ValidateArgumentType_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).ValidateArgumentType(ctx, req.(*ValidateArgumentTypeRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_WhoAmI_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(WhoAmIRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).WhoAmI(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_WhoAmI_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).WhoAmI(ctx, req.(*WhoAmIRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_SosReport_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SosReportRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).SosReport(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_SosReport_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).SosReport(ctx, req.(*SosReportRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_DumpVars_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DumpVarsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).DumpVars(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_DumpVars_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).DumpVars(ctx, req.(*DumpVarsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_DumpPublicIdActionMap_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DumpPublicIdActionMapRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).DumpPublicIdActionMap(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_DumpPublicIdActionMap_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).DumpPublicIdActionMap(ctx, req.(*DumpPublicIdActionMapRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_GetReadyz_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetReadyzRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).GetReadyz(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_GetReadyz_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).GetReadyz(ctx, req.(*GetReadyzRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_LocalUserLogin_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LocalUserLoginRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).LocalUserLogin(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_LocalUserLogin_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).LocalUserLogin(ctx, req.(*LocalUserLoginRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_PasswordHash_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(PasswordHashRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).PasswordHash(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_PasswordHash_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).PasswordHash(ctx, req.(*PasswordHashRequest))
}
return interceptor(ctx, in, info, handler)
}
func _OliveTinApiService_Logout_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(LogoutRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(OliveTinApiServiceServer).Logout(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: OliveTinApiService_Logout_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(OliveTinApiServiceServer).Logout(ctx, req.(*LogoutRequest))
}
return interceptor(ctx, in, info, handler)
}
// OliveTinApiService_ServiceDesc is the grpc.ServiceDesc for OliveTinApiService service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var OliveTinApiService_ServiceDesc = grpc.ServiceDesc{
ServiceName: "OliveTinApiService",
HandlerType: (*OliveTinApiServiceServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "GetDashboardComponents",
Handler: _OliveTinApiService_GetDashboardComponents_Handler,
},
{
MethodName: "StartAction",
Handler: _OliveTinApiService_StartAction_Handler,
},
{
MethodName: "StartActionAndWait",
Handler: _OliveTinApiService_StartActionAndWait_Handler,
},
{
MethodName: "StartActionByGet",
Handler: _OliveTinApiService_StartActionByGet_Handler,
},
{
MethodName: "StartActionByGetAndWait",
Handler: _OliveTinApiService_StartActionByGetAndWait_Handler,
},
{
MethodName: "KillAction",
Handler: _OliveTinApiService_KillAction_Handler,
},
{
MethodName: "ExecutionStatus",
Handler: _OliveTinApiService_ExecutionStatus_Handler,
},
{
MethodName: "GetLogs",
Handler: _OliveTinApiService_GetLogs_Handler,
},
{
MethodName: "ValidateArgumentType",
Handler: _OliveTinApiService_ValidateArgumentType_Handler,
},
{
MethodName: "WhoAmI",
Handler: _OliveTinApiService_WhoAmI_Handler,
},
{
MethodName: "SosReport",
Handler: _OliveTinApiService_SosReport_Handler,
},
{
MethodName: "DumpVars",
Handler: _OliveTinApiService_DumpVars_Handler,
},
{
MethodName: "DumpPublicIdActionMap",
Handler: _OliveTinApiService_DumpPublicIdActionMap_Handler,
},
{
MethodName: "GetReadyz",
Handler: _OliveTinApiService_GetReadyz_Handler,
},
{
MethodName: "LocalUserLogin",
Handler: _OliveTinApiService_LocalUserLogin_Handler,
},
{
MethodName: "PasswordHash",
Handler: _OliveTinApiService_PasswordHash_Handler,
},
{
MethodName: "Logout",
Handler: _OliveTinApiService_Logout_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "OliveTin.proto",
}

View File

@ -50,7 +50,4 @@ go-tools-all:
go install "github.com/bufbuild/buf/cmd/buf" go install "github.com/bufbuild/buf/cmd/buf"
go install "github.com/fzipp/gocyclo/cmd/gocyclo" go install "github.com/fzipp/gocyclo/cmd/gocyclo"
go install "github.com/go-critic/go-critic/cmd/gocritic" go install "github.com/go-critic/go-critic/cmd/gocritic"
go install "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
go install "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
go install "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
go install "google.golang.org/protobuf/cmd/protoc-gen-go" go install "google.golang.org/protobuf/cmd/protoc-gen-go"

View File

@ -1,8 +1,10 @@
module github.com/OliveTin/OliveTin module github.com/OliveTin/OliveTin
go 1.24 go 1.24.0
toolchain go1.24.4 toolchain go1.24.9
exclude google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884
require ( require (
connectrpc.com/connect v1.18.1 connectrpc.com/connect v1.18.1
@ -16,11 +18,11 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.2 github.com/golang-jwt/jwt/v5 v5.2.2
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/websocket v1.5.3 github.com/gorilla/websocket v1.5.3
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1
github.com/jamesread/golure v0.0.0-20250619190948-fa38cbd93cc4 github.com/jamesread/golure v0.0.0-20250619190948-fa38cbd93cc4
github.com/knadh/koanf/parsers/yaml v1.1.0 github.com/knadh/koanf/parsers/yaml v1.1.0
github.com/knadh/koanf/providers/env v1.1.0 github.com/knadh/koanf/providers/env v1.1.0
github.com/knadh/koanf/providers/file v1.2.0 github.com/knadh/koanf/providers/file v1.2.0
github.com/knadh/koanf/providers/rawbytes v1.0.0
github.com/knadh/koanf/v2 v2.3.0 github.com/knadh/koanf/v2 v2.3.0
github.com/prometheus/client_golang v1.22.0 github.com/prometheus/client_golang v1.22.0
github.com/robfig/cron/v3 v3.0.1 github.com/robfig/cron/v3 v3.0.1
@ -28,10 +30,8 @@ require (
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b
golang.org/x/oauth2 v0.30.0 golang.org/x/oauth2 v0.30.0
golang.org/x/sys v0.33.0 golang.org/x/sys v0.35.0
google.golang.org/grpc v1.73.0 google.golang.org/protobuf v1.36.10
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1
google.golang.org/protobuf v1.36.6
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
) )
@ -89,17 +89,16 @@ require (
github.com/google/cel-go v0.25.0 // indirect github.com/google/cel-go v0.25.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.6 // indirect github.com/google/go-containerregistry v0.20.6 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jdx/go-netrc v1.0.0 // indirect github.com/jdx/go-netrc v1.0.0 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/pgzip v1.2.6 // indirect github.com/klauspost/pgzip v1.2.6 // indirect
github.com/knadh/koanf/maps v0.1.2 // indirect github.com/knadh/koanf/maps v0.1.2 // indirect
github.com/knadh/koanf/providers/rawbytes v1.0.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.2 // indirect github.com/moby/term v0.5.2 // indirect
@ -140,17 +139,18 @@ require (
go.uber.org/mock v0.5.2 // indirect go.uber.org/mock v0.5.2 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect go.uber.org/zap v1.27.0 // indirect
go.yaml.in/yaml/v3 v3.0.3 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.39.0 // indirect golang.org/x/crypto v0.41.0 // indirect
golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect golang.org/x/exp/typeparams v0.0.0-20250620022241-b7579e27df2b // indirect
golang.org/x/mod v0.25.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.41.0 // indirect golang.org/x/net v0.43.0 // indirect
golang.org/x/sync v0.15.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/term v0.32.0 // indirect golang.org/x/term v0.34.0 // indirect
golang.org/x/text v0.26.0 // indirect golang.org/x/text v0.29.0 // indirect
golang.org/x/time v0.12.0 // indirect golang.org/x/time v0.12.0 // indirect
golang.org/x/tools v0.34.0 // indirect golang.org/x/tools v0.36.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
google.golang.org/grpc v1.75.1 // indirect
pluginrpc.com/pluginrpc v0.5.0 // indirect pluginrpc.com/pluginrpc v0.5.0 // indirect
) )

View File

@ -128,8 +128,6 @@ github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8= github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY= github.com/google/cel-go v0.25.0 h1:jsFw9Fhn+3y2kBbltZR4VEz5xKkcIFRPDnuEzAGv5GY=
github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI= github.com/google/cel-go v0.25.0/go.mod h1:hjEb6r5SuOSlhCHmFoLzu8HGCERvIsDAbxDAyNU/MmI=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@ -142,8 +140,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jamesread/golure v0.0.0-20250619190948-fa38cbd93cc4 h1:MIZEqAaeMP1/saH0w6I5mzGKSv2lw8fAO7Hm2FgJb9k= github.com/jamesread/golure v0.0.0-20250619190948-fa38cbd93cc4 h1:MIZEqAaeMP1/saH0w6I5mzGKSv2lw8fAO7Hm2FgJb9k=
@ -184,8 +182,6 @@ github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa1
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
@ -302,15 +298,15 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.3 h1:bXOww4E/J3f66rav3pX3m8w6jDE4knZjGOw8b5Y6iNE= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.3/go.mod h1:tBHosrYAkRZjRAOREWbDnBXUf08JOwYq++0QNwQiWzI= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
@ -321,8 +317,8 @@ golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -331,8 +327,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -340,8 +336,8 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -356,23 +352,23 @@ golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@ -381,22 +377,20 @@ golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA= google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY= google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc= google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1 h1:F29+wU6Ee6qgu9TddPgooOdaqsxTMunOoj8KA5yuS5A= google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE=
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.5.1/go.mod h1:5KF+wpkbTSbGcR9zteSqZV6fqFOWBl4Yde8En8MryZA= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@ -2,13 +2,15 @@ package acl
import ( import (
"context" "context"
"net/http"
"strings" "strings"
"connectrpc.com/connect"
"github.com/OliveTin/OliveTin/internal/auth"
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"google.golang.org/grpc/metadata"
) )
type PermissionBits int type PermissionBits int
@ -184,32 +186,60 @@ func IsAllowedKill(cfg *config.Config, user *AuthenticatedUser, action *config.A
return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action) return aclCheck(Kill, cfg.DefaultPermissions.Kill, cfg, "isAllowedKill", user, action)
} }
func getMetadataKeyOrEmpty(md metadata.MD, key string) string { func getHeaderKeyOrEmpty(headers http.Header, key string) string {
mdValues := md.Get(key) values := headers.Values(key)
if len(values) > 0 {
if len(mdValues) > 0 { return values[0]
return mdValues[0]
} }
return "" return ""
} }
// UserFromContext tries to find a user from a grpc context // UserFromContext tries to find a user from a Connect RPC context
func UserFromContext(ctx context.Context, cfg *config.Config) *AuthenticatedUser { func UserFromContext[T any](ctx context.Context, req *connect.Request[T], cfg *config.Config) *AuthenticatedUser {
var ret *AuthenticatedUser var ret *AuthenticatedUser
md, ok := metadata.FromIncomingContext(ctx) if req != nil {
if ok {
ret = &AuthenticatedUser{} ret = &AuthenticatedUser{}
ret.Username = getMetadataKeyOrEmpty(md, "username") // Only trust headers if explicitly configured
ret.UsergroupLine = getMetadataKeyOrEmpty(md, "usergroup") if cfg.AuthHttpHeaderUsername != "" {
ret.Provider = getMetadataKeyOrEmpty(md, "provider") ret.Username = getHeaderKeyOrEmpty(req.Header(), cfg.AuthHttpHeaderUsername)
}
buildUserAcls(cfg, ret) if cfg.AuthHttpHeaderUserGroup != "" {
ret.UsergroupLine = getHeaderKeyOrEmpty(req.Header(), cfg.AuthHttpHeaderUserGroup)
}
// Optional provider header; otherwise infer below
prov := getHeaderKeyOrEmpty(req.Header(), "provider")
if prov != "" {
ret.Provider = prov
}
// If no username from headers, fall back to local session cookie
if ret.Username == "" {
// Build a minimal http.Request to parse cookies from headers
dummy := &http.Request{Header: req.Header()}
if c, err := dummy.Cookie("olivetin-sid-local"); err == nil && c != nil && c.Value != "" {
if sess := auth.GetUserSession("local", c.Value); sess != nil {
if u := cfg.FindUserByUsername(sess.Username); u != nil {
ret.Username = u.Username
ret.UsergroupLine = u.Usergroup
ret.Provider = "local"
ret.SID = c.Value
} else {
log.WithFields(log.Fields{"username": sess.Username}).Warn("UserFromContext: local session user not in config")
}
} else {
log.WithFields(log.Fields{"sid": c.Value, "provider": "local"}).Warn("UserFromContext: stale local session")
}
}
}
if ret.Username != "" {
buildUserAcls(cfg, ret)
}
} }
if !ok || ret.Username == "" { if ret == nil || ret.Username == "" {
ret = UserGuest(cfg) ret = UserGuest(cfg)
} }

View File

@ -15,6 +15,7 @@ import (
"net/http" "net/http"
acl "github.com/OliveTin/OliveTin/internal/acl" acl "github.com/OliveTin/OliveTin/internal/acl"
auth "github.com/OliveTin/OliveTin/internal/auth"
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
entities "github.com/OliveTin/OliveTin/internal/entities" entities "github.com/OliveTin/OliveTin/internal/entities"
executor "github.com/OliveTin/OliveTin/internal/executor" executor "github.com/OliveTin/OliveTin/internal/executor"
@ -57,7 +58,7 @@ func (api *oliveTinAPI) KillAction(ctx ctx.Context, req *connect.Request[apiv1.K
return connect.NewResponse(ret), nil return connect.NewResponse(ret), nil
} }
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
api.killActionByTrackingId(user, action, execReqLogEntry, ret) api.killActionByTrackingId(user, action, execReqLogEntry, ret)
@ -94,7 +95,7 @@ func (api *oliveTinAPI) StartAction(ctx ctx.Context, req *connect.Request[apiv1.
return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.BindingId)) return nil, connect.NewError(connect.CodeNotFound, fmt.Errorf("action with ID %s not found", req.Msg.BindingId))
} }
authenticatedUser := acl.UserFromContext(ctx, api.cfg) authenticatedUser := acl.UserFromContext(ctx, req, api.cfg)
execReq := executor.ExecutionRequest{ execReq := executor.ExecutionRequest{
Binding: pair, Binding: pair,
@ -130,8 +131,32 @@ func (api *oliveTinAPI) PasswordHash(ctx ctx.Context, req *connect.Request[apiv1
func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) { func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[apiv1.LocalUserLoginRequest]) (*connect.Response[apiv1.LocalUserLoginResponse], error) {
match := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password) match := checkUserPassword(api.cfg, req.Msg.Username, req.Msg.Password)
response := connect.NewResponse(&apiv1.LocalUserLoginResponse{
Success: match,
})
if match { if match {
// grpc.SendHeader(ctx, metadata.Pairs("set-username", req.Username)) // Set authentication cookie for successful login
user := api.cfg.FindUserByUsername(req.Msg.Username)
if user != nil {
sid := uuid.NewString()
// Register the session in the session storage
auth.RegisterUserSession(api.cfg, "local", sid, user.Username)
log.WithFields(log.Fields{
"username": user.Username,
}).Info("LocalUserLogin: Session created and registered")
// Set the authentication cookie in the response headers
cookie := &http.Cookie{
Name: "olivetin-sid-local",
Value: sid,
MaxAge: 31556952, // 1 year
HttpOnly: true,
Path: "/",
}
response.Header().Set("Set-Cookie", cookie.String())
}
log.WithFields(log.Fields{ log.WithFields(log.Fields{
"username": req.Msg.Username, "username": req.Msg.Username,
@ -142,9 +167,7 @@ func (api *oliveTinAPI) LocalUserLogin(ctx ctx.Context, req *connect.Request[api
}).Warn("LocalUserLogin: User login failed.") }).Warn("LocalUserLogin: User login failed.")
} }
return connect.NewResponse(&apiv1.LocalUserLoginResponse{ return response, nil
Success: match,
}), nil
} }
func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) { func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionAndWaitRequest]) (*connect.Response[apiv1.StartActionAndWaitResponse], error) {
@ -154,7 +177,7 @@ func (api *oliveTinAPI) StartActionAndWait(ctx ctx.Context, req *connect.Request
args[arg.Name] = arg.Value args[arg.Name] = arg.Value
} }
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
execReq := executor.ExecutionRequest{ execReq := executor.ExecutionRequest{
Binding: api.executor.FindBindingByID(req.Msg.ActionId), Binding: api.executor.FindBindingByID(req.Msg.ActionId),
@ -185,7 +208,7 @@ func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[a
Binding: api.executor.FindBindingByID(req.Msg.ActionId), Binding: api.executor.FindBindingByID(req.Msg.ActionId),
TrackingID: uuid.NewString(), TrackingID: uuid.NewString(),
Arguments: args, Arguments: args,
AuthenticatedUser: acl.UserFromContext(ctx, api.cfg), AuthenticatedUser: acl.UserFromContext(ctx, req, api.cfg),
Cfg: api.cfg, Cfg: api.cfg,
} }
@ -199,7 +222,7 @@ func (api *oliveTinAPI) StartActionByGet(ctx ctx.Context, req *connect.Request[a
func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetAndWaitRequest]) (*connect.Response[apiv1.StartActionByGetAndWaitResponse], error) { func (api *oliveTinAPI) StartActionByGetAndWait(ctx ctx.Context, req *connect.Request[apiv1.StartActionByGetAndWaitRequest]) (*connect.Response[apiv1.StartActionByGetAndWaitResponse], error) {
args := make(map[string]string) args := make(map[string]string)
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
execReq := executor.ExecutionRequest{ execReq := executor.ExecutionRequest{
Binding: api.executor.FindBindingByID(req.Msg.ActionId), Binding: api.executor.FindBindingByID(req.Msg.ActionId),
@ -277,7 +300,7 @@ func getMostRecentExecutionStatusById(api *oliveTinAPI, actionId string) *execut
func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) { func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[apiv1.ExecutionStatusRequest]) (*connect.Response[apiv1.ExecutionStatusResponse], error) {
res := &apiv1.ExecutionStatusResponse{} res := &apiv1.ExecutionStatusResponse{}
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
var ile *executor.InternalLogEntry var ile *executor.InternalLogEntry
@ -300,9 +323,6 @@ func (api *oliveTinAPI) ExecutionStatus(ctx ctx.Context, req *connect.Request[ap
func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.LogoutRequest]) (*connect.Response[apiv1.LogoutResponse], error) { func (api *oliveTinAPI) Logout(ctx ctx.Context, req *connect.Request[apiv1.LogoutRequest]) (*connect.Response[apiv1.LogoutResponse], error) {
// user := acl.UserFromContext(ctx, cfg) // user := acl.UserFromContext(ctx, cfg)
// grpc.SendHeader(ctx, metadata.Pairs("logout-provider", user.Provider))
// grpc.SendHeader(ctx, metadata.Pairs("logout-sid", user.SID))
return nil, nil return nil, nil
} }
@ -312,14 +332,14 @@ func (api *oliveTinAPI) GetActionBinding(ctx ctx.Context, req *connect.Request[a
return connect.NewResponse(&apiv1.GetActionBindingResponse{ return connect.NewResponse(&apiv1.GetActionBindingResponse{
Action: buildAction(binding, &DashboardRenderRequest{ Action: buildAction(binding, &DashboardRenderRequest{
cfg: api.cfg, cfg: api.cfg,
AuthenticatedUser: acl.UserFromContext(ctx, api.cfg), AuthenticatedUser: acl.UserFromContext(ctx, req, api.cfg),
ex: api.executor, ex: api.executor,
}), }),
}), nil }), nil
} }
func (api *oliveTinAPI) GetDashboard(ctx ctx.Context, req *connect.Request[apiv1.GetDashboardRequest]) (*connect.Response[apiv1.GetDashboardResponse], error) { func (api *oliveTinAPI) GetDashboard(ctx ctx.Context, req *connect.Request[apiv1.GetDashboardRequest]) (*connect.Response[apiv1.GetDashboardResponse], error) {
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
if err := api.checkDashboardAccess(user); err != nil { if err := api.checkDashboardAccess(user); err != nil {
return nil, err return nil, err
@ -369,7 +389,7 @@ func (api *oliveTinAPI) buildCustomDashboardResponse(rr *DashboardRenderRequest,
} }
func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetLogsRequest]) (*connect.Response[apiv1.GetLogsResponse], error) { func (api *oliveTinAPI) GetLogs(ctx ctx.Context, req *connect.Request[apiv1.GetLogsRequest]) (*connect.Response[apiv1.GetLogsResponse], error) {
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
ret := &apiv1.GetLogsResponse{} ret := &apiv1.GetLogsResponse{}
@ -413,7 +433,7 @@ func (api *oliveTinAPI) ValidateArgumentType(ctx ctx.Context, req *connect.Reque
} }
func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *connect.Request[apiv1.WhoAmIRequest]) (*connect.Response[apiv1.WhoAmIResponse], error) { func (api *oliveTinAPI) WhoAmI(ctx ctx.Context, req *connect.Request[apiv1.WhoAmIRequest]) (*connect.Response[apiv1.WhoAmIResponse], error) {
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
res := &apiv1.WhoAmIResponse{ res := &apiv1.WhoAmIResponse{
AuthenticatedUser: user.Username, AuthenticatedUser: user.Username,
@ -497,7 +517,7 @@ func (api *oliveTinAPI) EventStream(ctx ctx.Context, req *connect.Request[apiv1.
client := &connectedClients{ client := &connectedClients{
channel: make(chan *apiv1.EventStreamResponse, 10), // Buffered channel to hold Events channel: make(chan *apiv1.EventStreamResponse, 10), // Buffered channel to hold Events
AuthenticatedUser: acl.UserFromContext(ctx, api.cfg), AuthenticatedUser: acl.UserFromContext(ctx, req, api.cfg),
} }
log.Infof("EventStream: client connected: %v", client.AuthenticatedUser.Username) log.Infof("EventStream: client connected: %v", client.AuthenticatedUser.Username)
@ -573,7 +593,7 @@ func (api *oliveTinAPI) GetDiagnostics(ctx ctx.Context, req *connect.Request[api
} }
func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitRequest]) (*connect.Response[apiv1.InitResponse], error) { func (api *oliveTinAPI) Init(ctx ctx.Context, req *connect.Request[apiv1.InitRequest]) (*connect.Response[apiv1.InitResponse], error) {
user := acl.UserFromContext(ctx, api.cfg) user := acl.UserFromContext(ctx, req, api.cfg)
res := &apiv1.InitResponse{ res := &apiv1.InitResponse{
ShowFooter: api.cfg.ShowFooter, ShowFooter: api.cfg.ShowFooter,

View File

@ -0,0 +1,136 @@
package auth
import (
"os"
"sync"
"time"
"github.com/OliveTin/OliveTin/internal/config"
"github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
)
// Session management for user authentication
type UserSession struct {
Username string
Expiry int64
}
type SessionProvider struct {
Sessions map[string]*UserSession
}
type SessionStorage struct {
Providers map[string]*SessionProvider
}
var (
sessionStorage *SessionStorage
sessionStorageMutex sync.RWMutex
)
func init() {
sessionStorage = &SessionStorage{
Providers: make(map[string]*SessionProvider),
}
}
// RegisterUserSession registers a user session
func RegisterUserSession(cfg *config.Config, provider string, sid string, username string) {
sessionStorageMutex.Lock()
defer sessionStorageMutex.Unlock()
if sessionStorage.Providers[provider] == nil {
sessionStorage.Providers[provider] = &SessionProvider{
Sessions: make(map[string]*UserSession),
}
}
if sessionStorage.Providers == nil {
sessionStorage.Providers = make(map[string]*SessionProvider)
}
sessionStorage.Providers[provider].Sessions[sid] = &UserSession{
Username: username,
Expiry: time.Now().Unix() + 31556952, // 1 year
}
saveUserSessions(cfg)
}
// GetUserSession retrieves a user session
func GetUserSession(provider string, sid string) *UserSession {
sessionStorageMutex.Lock()
defer sessionStorageMutex.Unlock()
if sessionStorage.Providers[provider] == nil {
return nil
}
session := sessionStorage.Providers[provider].Sessions[sid]
if session == nil {
return nil
}
if session.Expiry < time.Now().Unix() {
delete(sessionStorage.Providers[provider].Sessions, sid)
return nil
}
return session
}
// LoadUserSessions loads sessions from disk
func LoadUserSessions(cfg *config.Config) {
sessionStorageMutex.Lock()
defer sessionStorageMutex.Unlock()
data, err := os.ReadFile(cfg.GetDir() + "/sessions.yaml")
if err != nil {
logrus.WithError(err).Warn("Failed to read sessions.yaml file")
// Initialize empty session storage if file doesn't exist
if sessionStorage == nil {
sessionStorage = &SessionStorage{
Providers: make(map[string]*SessionProvider),
}
}
return
}
err = yaml.Unmarshal(data, &sessionStorage)
if err != nil {
logrus.WithError(err).Error("Failed to unmarshal sessions.yaml")
// Initialize empty session storage if unmarshal fails
if sessionStorage == nil {
sessionStorage = &SessionStorage{
Providers: make(map[string]*SessionProvider),
}
}
return
}
// Ensure sessionStorage and Providers are properly initialized
if sessionStorage == nil {
sessionStorage = &SessionStorage{
Providers: make(map[string]*SessionProvider),
}
}
if sessionStorage.Providers == nil {
sessionStorage.Providers = make(map[string]*SessionProvider)
}
}
func saveUserSessions(cfg *config.Config) {
out, err := yaml.Marshal(sessionStorage)
if err != nil {
logrus.WithError(err).Error("Failed to marshal session storage")
return
}
err = os.WriteFile(cfg.GetDir()+"/sessions.yaml", out, 0600)
if err != nil {
logrus.WithError(err).Error("Failed to write sessions.yaml file")
return
}
}

View File

@ -99,7 +99,6 @@ type Config struct {
ListenAddressSingleHTTPFrontend string ListenAddressSingleHTTPFrontend string
ListenAddressWebUI string ListenAddressWebUI string
ListenAddressRestActions string ListenAddressRestActions string
ListenAddressGrpcActions string
ListenAddressPrometheus string ListenAddressPrometheus string
ExternalRestAddress string ExternalRestAddress string
LogLevel string LogLevel string
@ -256,7 +255,6 @@ func DefaultConfigWithBasePort(basePort int) *Config {
config.ListenAddressSingleHTTPFrontend = fmt.Sprintf("0.0.0.0:%d", basePort) config.ListenAddressSingleHTTPFrontend = fmt.Sprintf("0.0.0.0:%d", basePort)
config.ListenAddressRestActions = fmt.Sprintf("localhost:%d", basePort+1) config.ListenAddressRestActions = fmt.Sprintf("localhost:%d", basePort+1)
config.ListenAddressGrpcActions = fmt.Sprintf("localhost:%d", basePort+2)
config.ListenAddressWebUI = fmt.Sprintf("localhost:%d", basePort+3) config.ListenAddressWebUI = fmt.Sprintf("localhost:%d", basePort+3)
config.ListenAddressPrometheus = fmt.Sprintf("localhost:%d", basePort+4) config.ListenAddressPrometheus = fmt.Sprintf("localhost:%d", basePort+4)

View File

@ -1,8 +1,9 @@
package config package config
import ( import (
"github.com/stretchr/testify/assert"
"testing" "testing"
"github.com/stretchr/testify/assert"
) )
func TestFindAction(t *testing.T) { func TestFindAction(t *testing.T) {
@ -51,3 +52,40 @@ func TestSetDir(t *testing.T) {
assert.Equal(t, "test", c.GetDir(), "SetDir") assert.Equal(t, "test", c.GetDir(), "SetDir")
} }
func TestFindUserByUsername(t *testing.T) {
c := DefaultConfig()
// Test with empty users list
assert.Nil(t, c.FindUserByUsername("nonexistent"), "Find user in empty list should return nil")
// Add test users
user1 := &LocalUser{
Username: "admin",
Usergroup: "admin",
Password: "adminpass",
}
user2 := &LocalUser{
Username: "guest",
Usergroup: "guest",
Password: "guestpass",
}
c.AuthLocalUsers.Users = append(c.AuthLocalUsers.Users, user1, user2)
// Test finding existing users
foundUser := c.FindUserByUsername("admin")
assert.NotNil(t, foundUser, "Find existing user 'admin'")
assert.Equal(t, "admin", foundUser.Username, "Found user should have correct username")
assert.Equal(t, "admin", foundUser.Usergroup, "Found user should have correct usergroup")
assert.Equal(t, "adminpass", foundUser.Password, "Found user should have correct password")
foundUser = c.FindUserByUsername("guest")
assert.NotNil(t, foundUser, "Find existing user 'guest'")
assert.Equal(t, "guest", foundUser.Username, "Found user should have correct username")
assert.Equal(t, "guest", foundUser.Usergroup, "Found user should have correct usergroup")
// Test finding non-existent user
assert.Nil(t, c.FindUserByUsername("nonexistent"), "Find non-existent user should return nil")
assert.Nil(t, c.FindUserByUsername(""), "Find empty username should return nil")
}

View File

@ -76,38 +76,40 @@ func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) {
} }
} }
// If authLocalUsers are not loaded by default unmarshaling, try manual unmarshaling
// This is a workaround for a koanf issue where nested struct fields are not unmarshaled correctly
if len(cfg.AuthLocalUsers.Users) == 0 && k.Exists("authLocalUsers") {
var authLocalUsers AuthLocalUsersConfig
err := k.Unmarshal("authLocalUsers", &authLocalUsers)
if err != nil {
log.Errorf("Error manually unmarshaling authLocalUsers: %v", err)
} else {
cfg.AuthLocalUsers = authLocalUsers
}
}
// Manual field assignment for other config fields that might not be unmarshaled correctly // Manual field assignment for other config fields that might not be unmarshaled correctly
if k.Exists("showFooter") { boolVal(k, "showFooter", &cfg.ShowFooter)
cfg.ShowFooter = k.Bool("showFooter") boolVal(k, "showNavigation", &cfg.ShowNavigation)
} boolVal(k, "checkForUpdates", &cfg.CheckForUpdates)
if k.Exists("showNavigation") { stringVal(k, "pageTitle", &cfg.PageTitle)
cfg.ShowNavigation = k.Bool("showNavigation") stringVal(k, "listenAddressSingleHTTPFrontend", &cfg.ListenAddressSingleHTTPFrontend)
} stringVal(k, "listenAddressWebUI", &cfg.ListenAddressWebUI)
if k.Exists("checkForUpdates") { stringVal(k, "listenAddressRestActions", &cfg.ListenAddressRestActions)
cfg.CheckForUpdates = k.Bool("checkForUpdates") stringVal(k, "listenAddressPrometheus", &cfg.ListenAddressPrometheus)
} boolVal(k, "useSingleHTTPFrontend", &cfg.UseSingleHTTPFrontend)
if k.Exists("pageTitle") { stringVal(k, "logLevel", &cfg.LogLevel)
cfg.PageTitle = k.String("pageTitle")
}
// Handle defaultPolicy nested struct // Handle defaultPolicy nested struct
if k.Exists("defaultPolicy") { if k.Exists("defaultPolicy") {
if k.Exists("defaultPolicy.showDiagnostics") { boolVal(k, "defaultPolicy.showDiagnostics", &cfg.DefaultPolicy.ShowDiagnostics)
cfg.DefaultPolicy.ShowDiagnostics = k.Bool("defaultPolicy.showDiagnostics") boolVal(k, "defaultPolicy.showLogList", &cfg.DefaultPolicy.ShowLogList)
}
if k.Exists("defaultPolicy.showLogList") {
cfg.DefaultPolicy.ShowLogList = k.Bool("defaultPolicy.showLogList")
}
} }
// Handle prometheus nested struct // Handle prometheus nested struct
if k.Exists("prometheus") { if k.Exists("prometheus") {
if k.Exists("prometheus.enabled") { boolVal(k, "prometheus.enabled", &cfg.Prometheus.Enabled)
cfg.Prometheus.Enabled = k.Bool("prometheus.enabled") boolVal(k, "prometheus.defaultGoMetrics", &cfg.Prometheus.DefaultGoMetrics)
}
if k.Exists("prometheus.defaultGoMetrics") {
cfg.Prometheus.DefaultGoMetrics = k.Bool("prometheus.defaultGoMetrics")
}
} }
metricConfigReloadedCount.Inc() metricConfigReloadedCount.Inc()
@ -123,6 +125,25 @@ func AppendSource(cfg *Config, k *koanf.Koanf, configPath string) {
var envRegex = regexp.MustCompile(`\${{ *?(\S+) *?}}`) var envRegex = regexp.MustCompile(`\${{ *?(\S+) *?}}`)
// Helper functions to reduce repetitive if/set chains
func stringVal(k *koanf.Koanf, key string, dest *string) {
if k.Exists(key) {
*dest = k.String(key)
}
}
func boolVal(k *koanf.Koanf, key string, dest *bool) {
if k.Exists(key) {
*dest = k.Bool(key)
}
}
func int64Val(k *koanf.Koanf, key string, dest *int64) {
if k.Exists(key) {
*dest = k.Int64(key)
}
}
func envDecodeHookFunc(from reflect.Type, to reflect.Type, data any) (any, error) { func envDecodeHookFunc(from reflect.Type, to reflect.Type, data any) (any, error) {
log.Debugf("envDecodeHookFunc called: from=%v, to=%v, data=%v", from, to, data) log.Debugf("envDecodeHookFunc called: from=%v, to=%v, data=%v", from, to, data)
if from.Kind() != reflect.String { if from.Kind() != reflect.String {

View File

@ -0,0 +1,168 @@
package config
import (
"os"
"testing"
"github.com/knadh/koanf/parsers/yaml"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/v2"
"github.com/stretchr/testify/assert"
)
func TestUserLoadingFromConfig(t *testing.T) {
// Create a temporary test config file
testConfig := `
authLocalUsers:
enabled: true
users:
- username: testuser1
usergroup: admin
password: password1
- username: testuser2
usergroup: guest
password: password2
actions:
- title: Test Action
shell: echo "test"
`
// Create temporary file
tmpFile, err := os.CreateTemp("", "test_config_*.yaml")
assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name())
// Write test config to file
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
tmpFile.Close()
// Load config using koanf
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
assert.NoError(t, err, "Should load config file")
// Create config struct and load it
cfg := &Config{}
AppendSource(cfg, k, tmpFile.Name())
// Test that authLocalUsers was loaded correctly
assert.True(t, cfg.AuthLocalUsers.Enabled, "AuthLocalUsers should be enabled")
assert.Equal(t, 2, len(cfg.AuthLocalUsers.Users), "Should load 2 users")
// Test individual users
user1 := cfg.FindUserByUsername("testuser1")
assert.NotNil(t, user1, "Should find testuser1")
assert.Equal(t, "testuser1", user1.Username, "User1 should have correct username")
assert.Equal(t, "admin", user1.Usergroup, "User1 should have correct usergroup")
assert.Equal(t, "password1", user1.Password, "User1 should have correct password")
user2 := cfg.FindUserByUsername("testuser2")
assert.NotNil(t, user2, "Should find testuser2")
assert.Equal(t, "testuser2", user2.Username, "User2 should have correct username")
assert.Equal(t, "guest", user2.Usergroup, "User2 should have correct usergroup")
assert.Equal(t, "password2", user2.Password, "User2 should have correct password")
// Test non-existent user
assert.Nil(t, cfg.FindUserByUsername("nonexistent"), "Should return nil for non-existent user")
}
func TestUserLoadingWithEmptyUsers(t *testing.T) {
// Test config with enabled but no users
testConfig := `
authLocalUsers:
enabled: true
users: []
actions:
- title: Test Action
shell: echo "test"
`
tmpFile, err := os.CreateTemp("", "test_config_empty_*.yaml")
assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name())
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
tmpFile.Close()
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
assert.NoError(t, err, "Should load config file")
cfg := &Config{}
AppendSource(cfg, k, tmpFile.Name())
assert.True(t, cfg.AuthLocalUsers.Enabled, "AuthLocalUsers should be enabled")
assert.Equal(t, 0, len(cfg.AuthLocalUsers.Users), "Should have 0 users")
assert.Nil(t, cfg.FindUserByUsername("anyuser"), "Should return nil for any user")
}
func TestUserLoadingWithDisabledAuth(t *testing.T) {
// Test config with disabled auth
testConfig := `
authLocalUsers:
enabled: false
users:
- username: testuser
usergroup: admin
password: password
actions:
- title: Test Action
shell: echo "test"
`
tmpFile, err := os.CreateTemp("", "test_config_disabled_*.yaml")
assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name())
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
tmpFile.Close()
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
assert.NoError(t, err, "Should load config file")
cfg := &Config{}
AppendSource(cfg, k, tmpFile.Name())
assert.False(t, cfg.AuthLocalUsers.Enabled, "AuthLocalUsers should be disabled")
assert.Equal(t, 1, len(cfg.AuthLocalUsers.Users), "Should still load users even when disabled")
// User should still be findable even when auth is disabled
user := cfg.FindUserByUsername("testuser")
assert.NotNil(t, user, "Should find user even when auth is disabled")
}
func TestUserLoadingWithoutAuthSection(t *testing.T) {
// Test config without authLocalUsers section
testConfig := `
actions:
- title: Test Action
shell: echo "test"
`
tmpFile, err := os.CreateTemp("", "test_config_no_auth_*.yaml")
assert.NoError(t, err, "Should create temporary file")
defer os.Remove(tmpFile.Name())
_, err = tmpFile.WriteString(testConfig)
assert.NoError(t, err, "Should write test config to file")
tmpFile.Close()
k := koanf.New(".")
err = k.Load(file.Provider(tmpFile.Name()), yaml.Parser())
assert.NoError(t, err, "Should load config file")
cfg := &Config{}
AppendSource(cfg, k, tmpFile.Name())
// Should have default values
assert.False(t, cfg.AuthLocalUsers.Enabled, "AuthLocalUsers should be disabled by default")
assert.Equal(t, 0, len(cfg.AuthLocalUsers.Users), "Should have 0 users by default")
assert.Nil(t, cfg.FindUserByUsername("anyuser"), "Should return nil for any user")
}

View File

@ -1,14 +1,11 @@
package httpservers package httpservers
import ( import (
"context"
"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/metadata"
"google.golang.org/protobuf/reflect/protoreflect"
"net/http" "net/http"
"strings" "strings"
log "github.com/sirupsen/logrus"
// apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1" // apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
@ -41,109 +38,7 @@ func parseHttpHeaderForAuth(cfg *config.Config, req *http.Request) (string, stri
} }
//gocyclo:ignore //gocyclo:ignore
func parseRequestMetadata(cfg *config.Config, ctx context.Context, req *http.Request) metadata.MD {
username := ""
usergroup := ""
provider := "unknown"
sid := ""
if cfg.AuthJwtHeader != "" {
username, usergroup = parseJwtHeader(cfg, req)
provider = "jwt-header"
}
if cfg.AuthJwtCookieName != "" {
username, usergroup = parseJwtCookie(cfg, req)
provider = "jwt-cookie"
}
if cfg.AuthHttpHeaderUsername != "" && username == "" {
username, usergroup = parseHttpHeaderForAuth(cfg, req)
provider = "http-header"
}
// if len(cfg.AuthOAuth2Providers) > 0 && username == "" {
// username, usergroup, sid = parseOAuth2Cookie(req)
// provider = "oauth2"
// }
if cfg.AuthLocalUsers.Enabled && username == "" {
username, usergroup, sid = parseLocalUserCookie(cfg, req)
provider = "local"
}
md := metadata.New(map[string]string{
"username": username,
"usergroup": usergroup,
"provider": provider,
"sid": sid,
})
log.Tracef("api request metadata: %+v", md)
return md
}
func parseJwtHeader(cfg *config.Config, req *http.Request) (string, string) { func parseJwtHeader(cfg *config.Config, req *http.Request) (string, string) {
// JWTs in the Authorization header are usually prefixed with "Bearer " which is not part of the JWT token. // JWTs in the Authorization header are usually prefixed with "Bearer " which is not part of the JWT token.
return parseJwt(cfg, strings.TrimPrefix(req.Header.Get(cfg.AuthJwtHeader), "Bearer ")) return parseJwt(cfg, strings.TrimPrefix(req.Header.Get(cfg.AuthJwtHeader), "Bearer "))
} }
func (h *OAuth2Handler) forwardResponseHandler(cfg *config.Config, ctx context.Context, w http.ResponseWriter, msg protoreflect.ProtoMessage) error {
md, ok := runtime.ServerMetadataFromContext(ctx)
if !ok {
log.Warn("Could not get ServerMetadata from context")
return nil
}
forwardResponseHandlerLoginLocalUser(cfg, md.HeaderMD, w)
h.forwardResponseHandlerLogout(cfg, md.HeaderMD, w)
return nil
}
func (h *OAuth2Handler) forwardResponseHandlerLogout(cfg *config.Config, md metadata.MD, w http.ResponseWriter) {
if getMetadataKeyOrEmpty(md, "logout-provider") != "" {
sid := getMetadataKeyOrEmpty(md, "logout-sid")
delete(h.registeredStates, sid)
http.SetCookie(
w,
&http.Cookie{
Name: "olivetin-sid-oauth",
MaxAge: 31556952, // 1 year
Value: "",
HttpOnly: true,
Path: "/",
},
)
deleteLocalUserSession(cfg, "local", sid)
http.SetCookie(
w,
&http.Cookie{
Name: "olivetin-sid-local",
MaxAge: 31556952, // 1 year
Value: "",
HttpOnly: true,
Path: "/",
},
)
w.Header().Set("Content-Type", "text/html")
// We cannot send a HTTP redirect here, because we don't have access to req.
w.Write([]byte("<script>window.location.href = '/';</script>"))
}
}
func getMetadataKeyOrEmpty(md metadata.MD, key string) string {
mdValues := md.Get(key)
if len(mdValues) > 0 {
return mdValues[0]
}
return ""
}

View File

@ -1,177 +0,0 @@
package httpservers
import (
"github.com/OliveTin/OliveTin/internal/config"
"github.com/OliveTin/OliveTin/internal/filehelper"
log "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"os"
"path/filepath"
"sync"
"time"
)
var sessionStorageMutex sync.Mutex
type UserSession struct {
Username string
Expiry int64
}
type SessionProvider struct {
Sessions map[string]*UserSession
}
type SessionStorage struct {
Providers map[string]*SessionProvider
}
var (
sessionStorage *SessionStorage
)
func registerSessionProviders() {
sessionStorage = &SessionStorage{
Providers: make(map[string]*SessionProvider),
}
registerSessionProvider("local")
registerSessionProvider("oauth2")
}
func registerSessionProvider(provider string) {
sessionStorage.Providers[provider] = &SessionProvider{
Sessions: make(map[string]*UserSession),
}
}
func deleteLocalUserSession(cfg *config.Config, provider string, sid string) {
sessionStorageMutex.Lock()
deleteLocalUserSessionBatch(provider, sid)
sessionStorageMutex.Unlock()
saveUserSessions(cfg)
}
func deleteLocalUserSessionBatch(provider string, sid string) {
log.WithFields(log.Fields{
"sid": sid,
"provider": provider,
}).Debug("Deleting user session")
if _, ok := sessionStorage.Providers[provider]; !ok {
return
}
delete(sessionStorage.Providers[provider].Sessions, sid)
}
func registerUserSession(cfg *config.Config, provider string, sid string, username string) {
sessionStorageMutex.Lock()
sessionStorage.Providers[provider].Sessions[sid] = &UserSession{
Username: username,
Expiry: time.Now().Unix() + 31556952, // 1 year
}
sessionStorageMutex.Unlock()
saveUserSessions(cfg)
}
func saveUserSessions(cfg *config.Config) {
sessionStorageMutex.Lock()
defer sessionStorageMutex.Unlock()
filename := filepath.Join(cfg.GetDir(), "sessions.db.yaml")
out, err := yaml.Marshal(sessionStorage)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Errorf("Failed to marshal session data to %v", filename)
return
}
filehelper.WriteFile(filename, out)
}
func loadUserSessions(cfg *config.Config) {
registerSessionProviders()
filename := filepath.Join(cfg.GetDir(), "sessions.db.yaml")
if _, err := os.Stat(filename); os.IsNotExist(err) {
return
}
data, err := os.ReadFile(filename)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Errorf("Failed to read %v", filename)
return
}
err = yaml.Unmarshal(data, &sessionStorage)
if err != nil {
log.WithFields(log.Fields{
"error": err,
}).Error("Failed to unmarshal sessions.local.db")
return
}
deleteExpiredSessions(cfg)
}
func deleteExpiredSessions(cfg *config.Config) {
sessionStorageMutex.Lock()
for provider, sessions := range sessionStorage.Providers {
for sid, session := range sessions.Sessions {
if session.Expiry < time.Now().Unix() {
deleteLocalUserSessionBatch(provider, sid)
}
}
}
sessionStorageMutex.Unlock()
saveUserSessions(cfg)
}
func getUserFromSession(cfg *config.Config, providerName string, sid string) *config.LocalUser {
provider, ok := sessionStorage.Providers[providerName]
if !ok {
log.WithFields(log.Fields{
"provider": providerName,
}).Warnf("Provider not found")
return nil
}
session, ok := provider.Sessions[sid]
if !ok {
log.WithFields(log.Fields{
"sid": sid,
"provider": providerName,
}).Warnf("Stale session")
return nil
}
user := cfg.FindUserByUsername(session.Username)
if user == nil {
log.WithFields(log.Fields{
"sid": sid,
"provider": providerName,
}).Warnf("User not found")
return nil
}
return user
}

View File

@ -1,11 +1,11 @@
package httpservers package httpservers
import ( import (
"github.com/OliveTin/OliveTin/internal/config"
"google.golang.org/grpc/metadata"
"net/http" "net/http"
"github.com/google/uuid" "github.com/OliveTin/OliveTin/internal/auth"
"github.com/OliveTin/OliveTin/internal/config"
log "github.com/sirupsen/logrus"
) )
func parseLocalUserCookie(cfg *config.Config, req *http.Request) (string, string, string) { func parseLocalUserCookie(cfg *config.Config, req *http.Request) (string, string, string) {
@ -17,39 +17,18 @@ func parseLocalUserCookie(cfg *config.Config, req *http.Request) (string, string
cookieValue := cookie.Value cookieValue := cookie.Value
user := getUserFromSession(cfg, "local", cookieValue) session := auth.GetUserSession("local", cookieValue)
if session == nil {
return "", "", ""
}
user := cfg.FindUserByUsername(session.Username)
if user == nil { if user == nil {
log.WithFields(log.Fields{
"username": session.Username,
}).Warnf("User not found in config")
return "", "", "" return "", "", ""
} }
return user.Username, user.Usergroup, cookie.Value return user.Username, user.Usergroup, cookie.Value
} }
func forwardResponseHandlerLoginLocalUser(cfg *config.Config, md metadata.MD, w http.ResponseWriter) error {
setUsername := getMetadataKeyOrEmpty(md, "set-username")
if setUsername != "" {
user := cfg.FindUserByUsername(setUsername)
if user == nil {
return nil
}
sid := uuid.NewString()
registerUserSession(cfg, "local", sid, user.Username)
http.SetCookie(
w,
&http.Cookie{
Name: "olivetin-sid-local",
Value: sid,
MaxAge: 31556952, // 1 year
HttpOnly: true,
Path: "/",
},
)
}
return nil
}

View File

@ -18,7 +18,6 @@ import (
config "github.com/OliveTin/OliveTin/internal/config" config "github.com/OliveTin/OliveTin/internal/config"
"github.com/OliveTin/OliveTin/internal/executor" "github.com/OliveTin/OliveTin/internal/executor"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"google.golang.org/grpc/metadata"
) )
func logDebugRequest(cfg *config.Config, source string, r *http.Request) { func logDebugRequest(cfg *config.Config, source string, r *http.Request) {
@ -56,12 +55,6 @@ func StartSingleHTTPFrontend(cfg *config.Config, ex *executor.Executor) {
log.Debugf("SingleFrontend HTTP API Req URL after rewrite: %v", r.URL.Path) log.Debugf("SingleFrontend HTTP API Req URL after rewrite: %v", r.URL.Path)
// Process HTTP headers for authentication and add to context
ctx := r.Context()
md := parseRequestMetadata(cfg, ctx, r)
ctx = metadata.NewIncomingContext(ctx, md)
r = r.WithContext(ctx)
apiHandler.ServeHTTP(w, r) apiHandler.ServeHTTP(w, r)
})) }))

View File

@ -19,7 +19,6 @@ type sosReportConfig struct {
ListenAddressSingleHTTPFrontend string ListenAddressSingleHTTPFrontend string
ListenAddressWebUI string ListenAddressWebUI string
ListenAddressRestActions string ListenAddressRestActions string
ListenAddressGrpcActions string
Timezone string Timezone string
TimeNow string TimeNow string
ConfigDirectory string ConfigDirectory string
@ -34,7 +33,6 @@ func configToSosreport(cfg *config.Config) *sosReportConfig {
ListenAddressSingleHTTPFrontend: cfg.ListenAddressSingleHTTPFrontend, ListenAddressSingleHTTPFrontend: cfg.ListenAddressSingleHTTPFrontend,
ListenAddressWebUI: cfg.ListenAddressWebUI, ListenAddressWebUI: cfg.ListenAddressWebUI,
ListenAddressRestActions: cfg.ListenAddressRestActions, ListenAddressRestActions: cfg.ListenAddressRestActions,
ListenAddressGrpcActions: cfg.ListenAddressGrpcActions,
Timezone: time.Now().Location().String(), Timezone: time.Now().Location().String(),
TimeNow: time.Now().String(), TimeNow: time.Now().String(),
ConfigDirectory: cfg.GetDir(), ConfigDirectory: cfg.GetDir(),

View File

@ -109,6 +109,19 @@ func getBasePort() int {
return basePort return basePort
} }
func getConfigPath(directory string) string {
joinedPath := filepath.Join(directory, "config.yaml")
configPath, err := filepath.Abs(joinedPath)
if err != nil {
log.WithError(err).Warnf("Error getting absolute path for %s", joinedPath)
return joinedPath
}
return configPath
}
func initConfig(configDir string) { func initConfig(configDir string) {
k := koanf.New(".") k := koanf.New(".")
k.Load(env.Provider(".", ".", nil), nil) k.Load(env.Provider(".", ".", nil), nil)
@ -130,7 +143,8 @@ func initConfig(configDir string) {
var firstConfigPath string var firstConfigPath string
for _, directory := range directories { for _, directory := range directories {
configPath := filepath.Join(directory, "config.yaml") configPath := getConfigPath(directory)
log.Debugf("Checking config path: %s", configPath) log.Debugf("Checking config path: %s", configPath)
if _, err := os.Stat(configPath); err != nil { if _, err := os.Stat(configPath); err != nil {

View File

@ -7,6 +7,5 @@ import (
_ "github.com/bufbuild/buf/cmd/buf" _ "github.com/bufbuild/buf/cmd/buf"
_ "github.com/fzipp/gocyclo/cmd/gocyclo" _ "github.com/fzipp/gocyclo/cmd/gocyclo"
_ "github.com/go-critic/go-critic/cmd/gocritic" _ "github.com/go-critic/go-critic/cmd/gocritic"
_ "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
_ "google.golang.org/protobuf/cmd/protoc-gen-go" _ "google.golang.org/protobuf/cmd/protoc-gen-go"
) )

View File

@ -8,7 +8,4 @@ WORKDIR /workspace
RUN go install -v "github.com/bufbuild/buf/cmd/buf" RUN go install -v "github.com/bufbuild/buf/cmd/buf"
RUN go install -v "github.com/fzipp/gocyclo/cmd/gocyclo" RUN go install -v "github.com/fzipp/gocyclo/cmd/gocyclo"
RUN go install -v "github.com/go-critic/go-critic/cmd/gocritic" RUN go install -v "github.com/go-critic/go-critic/cmd/gocritic"
RUN go install -v "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway"
RUN go install -v "github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2"
RUN go install -v "google.golang.org/grpc/cmd/protoc-gen-go-grpc"
RUN go install -v "google.golang.org/protobuf/cmd/protoc-gen-go" RUN go install -v "google.golang.org/protobuf/cmd/protoc-gen-go"